diff --git a/kmymoney/kmymoney.cpp b/kmymoney/kmymoney.cpp index 5d405fd4a..91585a749 100644 --- a/kmymoney/kmymoney.cpp +++ b/kmymoney/kmymoney.cpp @@ -1,7599 +1,7599 @@ /*************************************************************************** kmymoney.cpp ------------------- copyright : (C) 2000 by Michael Edwardes (C) 2007 by Thomas Baumgart ****************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include #include "kmymoney.h" // ---------------------------------------------------------------------------- // Std C++ / STL Includes #include #include #include // ---------------------------------------------------------------------------- // QT Includes #include #include // only for performance tests #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef KF5Holidays_FOUND #include #include #endif // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneyglobalsettings.h" #include "kmymoneyadaptor.h" #include "dialogs/settings/ksettingskmymoney.h" #include "dialogs/kbackupdlg.h" #include "dialogs/kenterscheduledlg.h" #include "dialogs/kconfirmmanualenterdlg.h" #include "dialogs/kmymoneypricedlg.h" #include "dialogs/kcurrencyeditdlg.h" #include "dialogs/kequitypriceupdatedlg.h" #include "dialogs/kmymoneyfileinfodlg.h" #include "dialogs/kfindtransactiondlg.h" #include "dialogs/knewbankdlg.h" #include "wizards/newinvestmentwizard/knewinvestmentwizard.h" #include "dialogs/knewaccountdlg.h" #include "dialogs/editpersonaldatadlg.h" #include "dialogs/kselectdatabasedlg.h" #include "dialogs/kcurrencycalculator.h" #include "dialogs/keditscheduledlg.h" #include "wizards/newloanwizard/keditloanwizard.h" #include "dialogs/kpayeereassigndlg.h" #include "dialogs/ktagreassigndlg.h" #include "dialogs/kcategoryreassigndlg.h" #include "wizards/endingbalancedlg/kendingbalancedlg.h" #include "dialogs/kbalancechartdlg.h" #include "dialogs/kgeneratesqldlg.h" #include "dialogs/kloadtemplatedlg.h" #include "dialogs/kgpgkeyselectiondlg.h" #include "dialogs/ktemplateexportdlg.h" #include "dialogs/transactionmatcher.h" #include "wizards/newuserwizard/knewuserwizard.h" #include "wizards/newaccountwizard/knewaccountwizard.h" #include "dialogs/kbalancewarning.h" #include "widgets/kmymoneyaccountselector.h" #include "widgets/kmymoneypayeecombo.h" #include "widgets/onlinejobmessagesview.h" #include "widgets/kmymoneymvccombo.h" #include "views/kmymoneyview.h" #include "views/konlinejoboutbox.h" #include "models/onlinejobmessagesmodel.h" #include "mymoney/mymoneyobject.h" #include "mymoney/mymoneyfile.h" #include "mymoney/mymoneyinstitution.h" #include "mymoney/mymoneyaccount.h" #include "mymoney/mymoneyaccountloan.h" #include "mymoney/mymoneysecurity.h" #include "mymoney/mymoneypayee.h" #include "mymoney/mymoneytag.h" #include "mymoney/mymoneybudget.h" #include "mymoney/mymoneysplit.h" #include "mymoney/mymoneyutils.h" #include "mymoney/mymoneystatement.h" #include "mymoney/storage/mymoneystoragedump.h" #include "mymoney/storage/imymoneystorage.h" #include "mymoney/mymoneyforecast.h" #include "mymoney/onlinejobmessage.h" #include "converter/mymoneystatementreader.h" #include "converter/mymoneytemplate.h" #include "plugins/interfaces/kmmviewinterface.h" #include "plugins/interfaces/kmmstatementinterface.h" #include "plugins/interfaces/kmmimportinterface.h" #include "plugins/interfaceloader.h" #include "plugins/onlinepluginextended.h" #include "pluginloader.h" #include "tasks/credittransfer.h" #include "icons/icons.h" #include "misc/webconnect.h" #include "storage/imymoneyserialize.h" #include "storage/mymoneystoragesql.h" #include #include "transactioneditor.h" #include "konlinetransferform.h" #include #include #include "kmymoneyutils.h" #include "kcreditswindow.h" #include "ledgerdelegate.h" #include "storageenums.h" #include "mymoneyenums.h" #include "dialogenums.h" #include "misc/platformtools.h" #ifdef KMM_DEBUG #include "mymoneytracer.h" #endif using namespace Icons; static constexpr char recoveryKeyId[] = "59B0F826D2B08440"; // define the default period to warn about an expiring recoverkey to 30 days // but allows to override this setting during build time #ifndef RECOVER_KEY_EXPIRATION_WARNING #define RECOVER_KEY_EXPIRATION_WARNING 30 #endif const QHash KMyMoneyApp::s_Actions { {Action::FileOpenDatabase, QStringLiteral("open_database")}, {Action::FileSaveAsDatabase, QStringLiteral("saveas_database")}, {Action::FileBackup, QStringLiteral("file_backup")}, {Action::FileImportGNC, QStringLiteral("file_import_gnc")}, {Action::FileImportStatement, QStringLiteral("file_import_statement")}, {Action::FileImportTemplate, QStringLiteral("file_import_template")}, {Action::FileExportTemplate, QStringLiteral("file_export_template")}, {Action::FilePersonalData, QStringLiteral("view_personal_data")}, #ifdef KMM_DEBUG {Action::FileDump, QStringLiteral("file_dump")}, #endif {Action::FileInformation, QStringLiteral("view_file_info")}, {Action::EditFindTransaction, QStringLiteral("edit_find_transaction")}, {Action::ViewTransactionDetail, QStringLiteral("view_show_transaction_detail")}, {Action::ViewHideReconciled, QStringLiteral("view_hide_reconciled_transactions")}, {Action::ViewHideCategories, QStringLiteral("view_hide_unused_categories")}, {Action::ViewShowAll, QStringLiteral("view_show_all_accounts")}, {Action::InstitutionNew, QStringLiteral("institution_new")}, {Action::InstitutionEdit, QStringLiteral("institution_edit")}, {Action::InstitutionDelete, QStringLiteral("institution_delete")}, {Action::AccountNew, QStringLiteral("account_new")}, {Action::AccountOpen, QStringLiteral("account_open")}, {Action::AccountStartReconciliation, QStringLiteral("account_reconcile")}, {Action::AccountFinishReconciliation, QStringLiteral("account_reconcile_finish")}, {Action::AccountPostponeReconciliation, QStringLiteral("account_reconcile_postpone")}, {Action::AccountEdit, QStringLiteral("account_edit")}, {Action::AccountDelete, QStringLiteral("account_delete")}, {Action::AccountClose, QStringLiteral("account_close")}, {Action::AccountReopen, QStringLiteral("account_reopen")}, {Action::AccountTransactionReport, QStringLiteral("account_transaction_report")}, {Action::AccountBalanceChart, QStringLiteral("account_chart")}, {Action::AccountOnlineMap, QStringLiteral("account_online_map")}, {Action::AccountOnlineUnmap, QStringLiteral("account_online_unmap")}, {Action::AccountUpdateMenu, QStringLiteral("account_online_update_menu")}, {Action::AccountUpdate, QStringLiteral("account_online_update")}, {Action::AccountUpdateAll, QStringLiteral("account_online_update_all")}, {Action::AccountCreditTransfer, QStringLiteral("account_online_new_credit_transfer")}, {Action::CategoryNew, QStringLiteral("category_new")}, {Action::CategoryEdit, QStringLiteral("category_edit")}, {Action::CategoryDelete, QStringLiteral("category_delete")}, {Action::ToolCurrencies, QStringLiteral("tools_currency_editor")}, {Action::ToolPrices, QStringLiteral("tools_price_editor")}, {Action::ToolUpdatePrices, QStringLiteral("tools_update_prices")}, {Action::ToolConsistency, QStringLiteral("tools_consistency_check")}, {Action::ToolPerformance, QStringLiteral("tools_performancetest")}, {Action::ToolSQL, QStringLiteral("tools_generate_sql")}, {Action::ToolCalculator, QStringLiteral("tools_kcalc")}, {Action::SettingsAllMessages, QStringLiteral("settings_enable_messages")}, {Action::HelpShow, QStringLiteral("help_show_tip")}, {Action::TransactionNew, QStringLiteral("transaction_new")}, {Action::TransactionEdit, QStringLiteral("transaction_edit")}, {Action::TransactionEnter, QStringLiteral("transaction_enter")}, {Action::TransactionEditSplits, QStringLiteral("transaction_editsplits")}, {Action::TransactionCancel, QStringLiteral("transaction_cancel")}, {Action::TransactionDelete, QStringLiteral("transaction_delete")}, {Action::TransactionDuplicate, QStringLiteral("transaction_duplicate")}, {Action::TransactionMatch, QStringLiteral("transaction_match")}, {Action::TransactionAccept, QStringLiteral("transaction_accept")}, {Action::TransactionToggleReconciled, QStringLiteral("transaction_mark_toggle")}, {Action::TransactionToggleCleared, QStringLiteral("transaction_mark_cleared")}, {Action::TransactionReconciled, QStringLiteral("transaction_mark_reconciled")}, {Action::TransactionNotReconciled, QStringLiteral("transaction_mark_notreconciled")}, {Action::TransactionSelectAll, QStringLiteral("transaction_select_all")}, {Action::TransactionGoToAccount, QStringLiteral("transaction_goto_account")}, {Action::TransactionGoToPayee, QStringLiteral("transaction_goto_payee")}, {Action::TransactionCreateSchedule, QStringLiteral("transaction_create_schedule")}, {Action::TransactionAssignNumber, QStringLiteral("transaction_assign_number")}, {Action::TransactionCombine, QStringLiteral("transaction_combine")}, {Action::TransactionCopySplits, QStringLiteral("transaction_copy_splits")}, {Action::TransactionMoveMenu, QStringLiteral("transaction_move_menu")}, {Action::TransactionMarkMenu, QStringLiteral("transaction_mark_menu")}, {Action::TransactionContextMarkMenu, QStringLiteral("transaction_context_mark_menu")}, {Action::InvestmentNew, QStringLiteral("investment_new")}, {Action::InvestmentEdit, QStringLiteral("investment_edit")}, {Action::InvestmentDelete, QStringLiteral("investment_delete")}, {Action::InvestmentOnlinePrice, QStringLiteral("investment_online_price_update")}, {Action::InvestmentManualPrice, QStringLiteral("investment_manual_price_update")}, {Action::ScheduleNew, QStringLiteral("schedule_new")}, {Action::ScheduleEdit, QStringLiteral("schedule_edit")}, {Action::ScheduleDelete, QStringLiteral("schedule_delete")}, {Action::ScheduleDuplicate, QStringLiteral("schedule_duplicate")}, {Action::ScheduleEnter, QStringLiteral("schedule_enter")}, {Action::ScheduleSkip, QStringLiteral("schedule_skip")}, {Action::PayeeNew, QStringLiteral("payee_new")}, {Action::PayeeRename, QStringLiteral("payee_rename")}, {Action::PayeeDelete, QStringLiteral("payee_delete")}, {Action::PayeeMerge, QStringLiteral("payee_merge")}, {Action::TagNew, QStringLiteral("tag_new")}, {Action::TagRename, QStringLiteral("tag_rename")}, {Action::TagDelete, QStringLiteral("tag_delete")}, {Action::BudgetNew, QStringLiteral("budget_new")}, {Action::BudgetRename, QStringLiteral("budget_rename")}, {Action::BudgetDelete, QStringLiteral("budget_delete")}, {Action::BudgetCopy, QStringLiteral("budget_copy")}, {Action::BudgetChangeYear, QStringLiteral("budget_change_year")}, {Action::BudgetForecast, QStringLiteral("budget_forecast")}, {Action::CurrencyNew, QStringLiteral("currency_new")}, {Action::CurrencyRename, QStringLiteral("currency_rename")}, {Action::CurrencyDelete, QStringLiteral("currency_delete")}, {Action::CurrencySetBase, QStringLiteral("currency_setbase")}, {Action::PriceNew, QStringLiteral("price_new")}, {Action::PriceEdit, QStringLiteral("price_edit")}, {Action::PriceUpdate, QStringLiteral("price_update")}, {Action::PriceDelete, QStringLiteral("price_delete")}, #ifdef KMM_DEBUG {Action::WizardNewUser, QStringLiteral("new_user_wizard")}, {Action::DebugTraces, QStringLiteral("debug_traces")}, #endif {Action::DebugTimers, QStringLiteral("debug_timers")}, {Action::OnlineJobDelete, QStringLiteral("onlinejob_delete")}, {Action::OnlineJobEdit, QStringLiteral("onlinejob_edit")}, {Action::OnlineJobLog, QStringLiteral("onlinejob_log")}, }; enum backupStateE { BACKUP_IDLE = 0, BACKUP_MOUNTING, BACKUP_COPYING, BACKUP_UNMOUNTING }; class KMyMoneyApp::Private { public: Private(KMyMoneyApp *app) : q(app), m_ft(0), m_moveToAccountSelector(0), m_statementXMLindex(0), m_balanceWarning(0), m_collectingStatements(false), m_pluginLoader(0), m_backupResult(0), m_backupMount(0), m_ignoreBackupExitCode(false), m_myMoneyView(0), m_progressBar(0), m_smtReader(0), m_searchDlg(0), m_autoSaveTimer(0), m_progressTimer(0), m_inAutoSaving(false), m_transactionEditor(0), m_endingBalanceDlg(0), m_saveEncrypted(0), m_additionalKeyLabel(0), m_additionalKeyButton(0), m_recentFiles(0), #ifdef KF5Holidays_FOUND m_holidayRegion(0), #endif m_applicationIsReady(true), m_webConnect(new WebConnect(app)) { // since the days of the week are from 1 to 7, // and a day of the week is used to index this bit array, // resize the array to 8 elements (element 0 is left unused) m_processingDays.resize(8); } void closeFile(); void unlinkStatementXML(); void moveInvestmentTransaction(const QString& fromId, const QString& toId, const MyMoneyTransaction& t); QList > automaticReconciliation(const MyMoneyAccount &account, const QList > &transactions, const MyMoneyMoney &amount); /** * The public interface. */ KMyMoneyApp * const q; MyMoneyFileTransaction* m_ft; KMyMoneyAccountSelector* m_moveToAccountSelector; int m_statementXMLindex; KBalanceWarning* m_balanceWarning; bool m_collectingStatements; QStringList m_statementResults; KMyMoneyPlugin::PluginLoader* m_pluginLoader; QString m_lastPayeeEnteredId; /** the configuration object of the application */ KSharedConfigPtr m_config; /** * @brief List of all plugged plugins * * The key is the file name of the plugin. */ QMap m_plugins; /** * @brief List of plugged importer plugins * * The key is the objectName of the plugin. */ QMap m_importerPlugins; /** * @brief List of plugged online plugins * * The key is the objectName of the plugin. */ QMap m_onlinePlugins; /** * The following variable represents the state while crafting a backup. * It can have the following values * * - IDLE: the default value if not performing a backup * - MOUNTING: when a mount command has been issued * - COPYING: when a copy command has been issued * - UNMOUNTING: when an unmount command has been issued */ backupStateE m_backupState; /** * This variable keeps the result of the backup operation. */ int m_backupResult; /** * This variable is set, when the user selected to mount/unmount * the backup volume. */ bool m_backupMount; /** * Flag for internal run control */ bool m_ignoreBackupExitCode; KProcess m_proc; /// A pointer to the view holding the tabs. KMyMoneyView *m_myMoneyView; /// The URL of the file currently being edited when open. QUrl m_fileName; bool m_startDialog; QString m_mountpoint; QProgressBar* m_progressBar; QTime m_lastUpdate; QLabel* m_statusLabel; MyMoneyStatementReader* m_smtReader; // allows multiple imports to be launched trough web connect and to be executed sequentially QQueue m_importUrlsQueue; KFindTransactionDlg* m_searchDlg; QObject* m_pluginInterface; MyMoneyAccount m_selectedAccount; MyMoneyAccount m_reconciliationAccount; MyMoneyAccount m_selectedInvestment; MyMoneyInstitution m_selectedInstitution; MyMoneySchedule m_selectedSchedule; MyMoneySecurity m_selectedCurrency; MyMoneyPrice m_selectedPrice; QList m_selectedPayees; QList m_selectedTags; QList m_selectedBudgets; KMyMoneyRegister::SelectedTransactions m_selectedTransactions; // This is Auto Saving related bool m_autoSaveEnabled; QTimer* m_autoSaveTimer; QTimer* m_progressTimer; int m_autoSavePeriod; bool m_inAutoSaving; // pointer to the current transaction editor TransactionEditor* m_transactionEditor; // Reconciliation dialog KEndingBalanceDlg* m_endingBalanceDlg; // Pointer to the combo box used for key selection during // File/Save as KComboBox* m_saveEncrypted; // id's that need to be remembered QString m_accountGoto, m_payeeGoto; QStringList m_additionalGpgKeys; QLabel* m_additionalKeyLabel; QPushButton* m_additionalKeyButton; KRecentFilesAction* m_recentFiles; #ifdef KF5Holidays_FOUND // used by the calendar interface for schedules KHolidays::HolidayRegion* m_holidayRegion; #endif QBitArray m_processingDays; QMap m_holidayMap; QStringList m_consistencyCheckResult; bool m_applicationIsReady; WebConnect* m_webConnect; // methods void consistencyCheck(bool alwaysDisplayResults); static void setThemedCSS(); void copyConsistencyCheckResults(); void saveConsistencyCheckResults(); }; KMyMoneyApp::KMyMoneyApp(QWidget* parent) : KXmlGuiWindow(parent), d(new Private(this)) { #ifdef KMM_DBUS new KmymoneyAdaptor(this); QDBusConnection::sessionBus().registerObject("/KMymoney", this); QDBusConnection::sessionBus().interface()->registerService( "org.kde.kmymoney", QDBusConnectionInterface::DontQueueService); #endif // Register the main engine types used as meta-objects qRegisterMetaType("MyMoneyMoney"); qRegisterMetaType("MyMoneySecurity"); // preset the pointer because we need it during the course of this constructor kmymoney = this; d->m_config = KSharedConfig::openConfig(); d->setThemedCSS(); MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneyGlobalSettings::firstFiscalMonth(), KMyMoneyGlobalSettings::firstFiscalDay()); updateCaption(true); QFrame* frame = new QFrame; frame->setFrameStyle(QFrame::NoFrame); // values for margin (11) and spacing(6) taken from KDialog implementation QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom, frame); layout->setContentsMargins(2, 2, 2, 2); layout->setSpacing(6); { QString themeName = KMyMoneySettings::iconsTheme(); // get theme user wants if (!themeName.isEmpty() && themeName != QLatin1Literal("system")) // if it isn't default theme then set it QIcon::setThemeName(themeName); Icons::setIconThemeNames(QIcon::themeName()); // get whatever theme user ends up with and hope our icon names will fit that theme } initStatusBar(); initActions(); initDynamicMenus(); d->m_myMoneyView = new KMyMoneyView(this/*the global variable kmymoney is not yet assigned. So we pass it here*/); layout->addWidget(d->m_myMoneyView, 10); connect(d->m_myMoneyView, &KMyMoneyView::aboutToChangeView, this, &KMyMoneyApp::slotResetSelections); connect(d->m_myMoneyView, SIGNAL(currentPageChanged(KPageWidgetItem*,KPageWidgetItem*)), this, SLOT(slotUpdateActions())); connectActionsAndViews(); /////////////////////////////////////////////////////////////////// // call inits to invoke all other construction parts readOptions(); // now initialize the plugin structure createInterfaces(); loadPlugins(); setCentralWidget(frame); connect(&d->m_proc, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotBackupHandleEvents())); // force to show the home page if the file is closed connect(actionCollection()->action(s_Actions[Action::ViewTransactionDetail]), &QAction::toggled, d->m_myMoneyView, &KMyMoneyView::slotShowTransactionDetail); d->m_backupState = BACKUP_IDLE; QLocale locale; int weekStart = locale.firstDayOfWeek(); int weekEnd = weekStart-1; if (weekEnd < Qt::Monday) { weekEnd = Qt::Sunday; } bool startFirst = (weekStart < weekEnd); for (int i = 0; i < 8; i++) { if (startFirst) d->m_processingDays.setBit(i, (i >= weekStart && i <= weekEnd)); else d->m_processingDays.setBit(i, (i >= weekStart || i <= weekEnd)); } d->m_autoSaveTimer = new QTimer(this); d->m_progressTimer = new QTimer(this); connect(d->m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); connect(d->m_progressTimer, SIGNAL(timeout()), this, SLOT(slotStatusProgressDone())); // make sure, we get a note when the engine changes state connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotDataChanged())); // connect the WebConnect server connect(d->m_webConnect, SIGNAL(gotUrl(QUrl)), this, SLOT(webConnect(QUrl))); // make sure we have a balance warning object d->m_balanceWarning = new KBalanceWarning(this); // setup the initial configuration slotUpdateConfiguration(); // kickstart date change timer slotDateChanged(); connect(this, SIGNAL(fileLoaded(QUrl)), onlineJobAdministration::instance(), SLOT(updateOnlineTaskProperties())); } KMyMoneyApp::~KMyMoneyApp() { // delete cached objects since the are in the way // when unloading the plugins onlineJobAdministration::instance()->clearCaches(); // we need to unload all plugins before we destroy anything else unloadPlugins(); delete d->m_searchDlg; delete d->m_transactionEditor; delete d->m_endingBalanceDlg; delete d->m_moveToAccountSelector; #ifdef KF5Holidays_FOUND delete d->m_holidayRegion; #endif delete d; } QUrl KMyMoneyApp::lastOpenedURL() { QUrl url = d->m_startDialog ? QUrl() : d->m_fileName; if (!url.isValid()) { url = QUrl::fromUserInput(readLastUsedFile()); } ready(); return url; } void KMyMoneyApp::slotObjectDestroyed(QObject* o) { if (o == d->m_moveToAccountSelector) { d->m_moveToAccountSelector = 0; } } void KMyMoneyApp::slotInstallConsistencyCheckContextMenu() { // this code relies on the implementation of KMessageBox::informationList to add a context menu to that list, // please adjust it if it's necessary or rewrite the way the consistency check results are displayed if (QWidget* dialog = QApplication::activeModalWidget()) { if (QListWidget* widget = dialog->findChild()) { // give the user a hint that the data can be saved widget->setToolTip(i18n("This is the consistency check log, use the context menu to copy or save it.")); widget->setWhatsThis(widget->toolTip()); widget->setContextMenuPolicy(Qt::CustomContextMenu); connect(widget, SIGNAL(customContextMenuRequested(QPoint)), SLOT(slotShowContextMenuForConsistencyCheck(QPoint))); } } } void KMyMoneyApp::slotShowContextMenuForConsistencyCheck(const QPoint &pos) { // allow the user to save the consistency check results if (QWidget* widget = qobject_cast< QWidget* >(sender())) { QMenu contextMenu(widget); QAction* copy = new QAction(i18n("Copy to clipboard"), widget); QAction* save = new QAction(i18n("Save to file"), widget); contextMenu.addAction(copy); contextMenu.addAction(save); QAction *result = contextMenu.exec(widget->mapToGlobal(pos)); if (result == copy) { // copy the consistency check results to the clipboard d->copyConsistencyCheckResults(); } else if (result == save) { // save the consistency check results to a file d->saveConsistencyCheckResults(); } } } void KMyMoneyApp::createTransactionMoveMenu() { if (!d->m_moveToAccountSelector) { QWidget* w = factory()->container("transaction_move_menu", this); QMenu *menu = dynamic_cast(w); if (menu) { QWidgetAction *accountSelectorAction = new QWidgetAction(menu); d->m_moveToAccountSelector = new KMyMoneyAccountSelector(menu, 0, false); d->m_moveToAccountSelector->setObjectName("transaction_move_menu_selector"); accountSelectorAction->setDefaultWidget(d->m_moveToAccountSelector); menu->addAction(accountSelectorAction); connect(d->m_moveToAccountSelector, SIGNAL(destroyed(QObject*)), this, SLOT(slotObjectDestroyed(QObject*))); connect(d->m_moveToAccountSelector, SIGNAL(itemSelected(QString)), this, SLOT(slotMoveToAccount(QString))); } } } void KMyMoneyApp::initDynamicMenus() { connect(this, SIGNAL(accountSelected(MyMoneyAccount)), this, SLOT(slotUpdateMoveToAccountMenu())); connect(this, SIGNAL(transactionsSelected(KMyMoneyRegister::SelectedTransactions)), this, SLOT(slotUpdateMoveToAccountMenu())); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotUpdateMoveToAccountMenu())); } void KMyMoneyApp::initActions() { KActionCollection *aC = actionCollection(); // ************* // Adding standard actions // ************* KStandardAction::openNew(this, &KMyMoneyApp::slotFileNew, aC); KStandardAction::open(this, &KMyMoneyApp::slotFileOpen, aC); d->m_recentFiles = KStandardAction::openRecent(this, &KMyMoneyApp::slotFileOpenRecent, aC); KStandardAction::save(this, &KMyMoneyApp::slotFileSave, aC); KStandardAction::saveAs(this, &KMyMoneyApp::slotFileSaveAs, aC); KStandardAction::close(this, &KMyMoneyApp::slotFileClose, aC); KStandardAction::quit(this, &KMyMoneyApp::slotFileQuit, aC); KStandardAction::print(this, &KMyMoneyApp::slotPrintView, aC); KStandardAction::preferences(this, &KMyMoneyApp::slotSettings, aC); /* Look-up table for all custom actions. It's required for: 1) building QList with QActions to be added to ActionCollection 2) adding custom features to QActions like e.g. overlaid icon or keyboard shortcut */ QHash lutActions; // ************* // Adding all actions // ************* { // struct for QAction's vital (except icon) information struct actionInfo { Action action; KMyMoneyAppFunc callback; QString text; Icon icon; }; const QVector actionInfos { // ************* // The File menu // ************* {Action::FileOpenDatabase, &KMyMoneyApp::slotOpenDatabase, i18n("Open database..."), Icon::SVNUpdate}, {Action::FileSaveAsDatabase, &KMyMoneyApp::slotSaveAsDatabase, i18n("Save as database..."), Icon::FileArchiver}, {Action::FileBackup, &KMyMoneyApp::slotBackupFile, i18n("Backup..."), Icon::Empty}, {Action::FileImportGNC, &KMyMoneyApp::slotGncImport, i18n("GnuCash..."), Icon::Empty}, {Action::FileImportStatement, &KMyMoneyApp::slotStatementImport, i18n("Statement file..."), Icon::Empty}, {Action::FileImportTemplate, &KMyMoneyApp::slotLoadAccountTemplates, i18n("Account Template..."), Icon::Empty}, {Action::FileExportTemplate, &KMyMoneyApp::slotSaveAccountTemplates, i18n("Account Template..."), Icon::Empty}, {Action::FilePersonalData, &KMyMoneyApp::slotFileViewPersonal, i18n("Personal Data..."), Icon::UserProperties}, #ifdef KMM_DEBUG {Action::FileDump, &KMyMoneyApp::slotFileFileInfo, i18n("Dump Memory"), Icon::Empty}, #endif {Action::FileInformation, &KMyMoneyApp::slotFileInfoDialog, i18n("File-Information..."), Icon::DocumentProperties}, // ************* // The Edit menu // ************* {Action::EditFindTransaction, &KMyMoneyApp::slotFindTransaction, i18n("Find transaction..."), Icon::Empty}, // ************* // The View menu // ************* {Action::ViewTransactionDetail, &KMyMoneyApp::slotShowTransactionDetail, i18n("Show Transaction Detail"), Icon::ViewTransactionDetail}, {Action::ViewHideReconciled, &KMyMoneyApp::slotHideReconciledTransactions, i18n("Hide reconciled transactions"), Icon::HideReconciled}, {Action::ViewHideCategories, &KMyMoneyApp::slotHideUnusedCategories, i18n("Hide unused categories"), Icon::HideCategories}, {Action::ViewShowAll, &KMyMoneyApp::slotShowAllAccounts, i18n("Show all accounts"), Icon::Empty}, // ********************* // The institutions menu // ********************* {Action::InstitutionNew, &KMyMoneyApp::slotInstitutionNew, i18n("New institution..."), Icon::Empty}, {Action::InstitutionEdit, &KMyMoneyApp::slotInstitutionEditEmpty, i18n("Edit institution..."), Icon::Empty}, {Action::InstitutionDelete, &KMyMoneyApp::slotInstitutionDelete, i18n("Delete institution..."), Icon::Empty}, // ***************** // The accounts menu // ***************** {Action::AccountNew, &KMyMoneyApp::slotAccountNew, i18n("New account..."), Icon::Empty}, {Action::AccountOpen, &KMyMoneyApp::slotAccountOpenEmpty, i18n("Open ledger"), Icon::ViewFinancialList}, {Action::AccountStartReconciliation, &KMyMoneyApp::slotAccountReconcileStart, i18n("Reconcile..."), Icon::Reconcile}, {Action::AccountFinishReconciliation, &KMyMoneyApp::slotAccountReconcileFinish, i18nc("Finish reconciliation", "Finish"), Icon::Empty}, {Action::AccountPostponeReconciliation, &KMyMoneyApp::slotAccountReconcilePostpone, i18n("Postpone reconciliation"), Icon::MediaPlaybackPause}, {Action::AccountEdit, &KMyMoneyApp::slotAccountEdit, i18n("Edit account..."), Icon::Empty}, {Action::AccountDelete, &KMyMoneyApp::slotAccountDelete, i18n("Delete account..."), Icon::Empty}, {Action::AccountClose, &KMyMoneyApp::slotAccountClose, i18n("Close account"), Icon::Empty}, {Action::AccountReopen, &KMyMoneyApp::slotAccountReopen, i18n("Reopen account"), Icon::Empty}, {Action::AccountTransactionReport, &KMyMoneyApp::slotAccountTransactionReport, i18n("Transaction report"), Icon::ViewFinancialList}, {Action::AccountBalanceChart, &KMyMoneyApp::slotAccountChart, i18n("Show balance chart..."), Icon::OfficeChartLine}, {Action::AccountOnlineMap, &KMyMoneyApp::slotAccountMapOnline, i18n("Map account..."), Icon::NewsSubscribe}, {Action::AccountOnlineUnmap, &KMyMoneyApp::slotAccountUnmapOnline, i18n("Unmap account..."), Icon::NewsUnsubscribe}, {Action::AccountUpdateMenu, &KMyMoneyApp::slotAccountUpdateOnline, i18nc("Update online accounts menu", "Update"), Icon::Empty}, {Action::AccountUpdate, &KMyMoneyApp::slotAccountUpdateOnline, i18n("Update account..."), Icon::Empty}, {Action::AccountUpdateAll, &KMyMoneyApp::slotAccountUpdateOnlineAll, i18n("Update all accounts..."), Icon::Empty}, {Action::AccountCreditTransfer, &KMyMoneyApp::slotNewOnlineTransfer, i18n("New credit transfer"), Icon::Empty}, // ******************* // The categories menu // ******************* {Action::CategoryNew, &KMyMoneyApp::slotCategoryNew, i18n("New category..."), Icon::Empty}, {Action::CategoryEdit, &KMyMoneyApp::slotAccountEdit, i18n("Edit category..."), Icon::Empty}, {Action::CategoryDelete, &KMyMoneyApp::slotAccountDelete, i18n("Delete category..."), Icon::Empty}, // ************** // The tools menu // ************** {Action::ToolCurrencies, &KMyMoneyApp::slotCurrencyDialog, i18n("Currencies..."), Icon::ViewCurrencyList}, {Action::ToolPrices, &KMyMoneyApp::slotPriceDialog, i18n("Prices..."), Icon::Empty}, {Action::ToolUpdatePrices, &KMyMoneyApp::slotEquityPriceUpdate, i18n("Update Stock and Currency Prices..."), Icon::Empty}, {Action::ToolConsistency, &KMyMoneyApp::slotFileConsistencyCheck, i18n("Consistency Check"), Icon::Empty}, {Action::ToolPerformance, &KMyMoneyApp::slotPerformanceTest, i18n("Performance-Test"), Icon::Fork}, {Action::ToolSQL, &KMyMoneyApp::slotGenerateSql, i18n("Generate Database SQL"), Icon::Empty}, {Action::ToolCalculator, &KMyMoneyApp::slotToolsStartKCalc, i18n("Calculator..."), Icon::AccessoriesCalculator}, // ***************** // The settings menu // ***************** {Action::SettingsAllMessages, &KMyMoneyApp::slotEnableMessages, i18n("Enable all messages"), Icon::Empty}, // ************* // The help menu // ************* {Action::HelpShow, &KMyMoneyApp::slotShowTipOfTheDay, i18n("&Show tip of the day"), Icon::Tip}, // *************************** // Actions w/o main menu entry // *************************** {Action::TransactionNew, &KMyMoneyApp::slotTransactionsNew, i18nc("New transaction button", "New"), Icon::Empty}, {Action::TransactionEdit, &KMyMoneyApp::slotTransactionsEdit, i18nc("Edit transaction button", "Edit"), Icon::Empty}, {Action::TransactionEnter, &KMyMoneyApp::slotTransactionsEnter, i18nc("Enter transaction", "Enter"), Icon::DialogOK}, {Action::TransactionEditSplits, &KMyMoneyApp::slotTransactionsEditSplits, i18nc("Edit split button", "Edit splits"), Icon::Split}, {Action::TransactionCancel, &KMyMoneyApp::slotTransactionsCancel, i18nc("Cancel transaction edit", "Cancel"), Icon::DialogCancel}, {Action::TransactionDelete, &KMyMoneyApp::slotTransactionsDelete, i18nc("Delete transaction", "Delete"), Icon::EditDelete}, {Action::TransactionDuplicate, &KMyMoneyApp::slotTransactionDuplicate, i18nc("Duplicate transaction", "Duplicate"), Icon::EditCopy}, {Action::TransactionMatch, &KMyMoneyApp::slotTransactionMatch, i18nc("Button text for match transaction", "Match"),Icon::Empty}, {Action::TransactionAccept, &KMyMoneyApp::slotTransactionsAccept, i18nc("Accept 'imported' and 'matched' transaction", "Accept"), Icon::Empty}, {Action::TransactionToggleReconciled, &KMyMoneyApp::slotToggleReconciliationFlag, i18nc("Toggle reconciliation flag", "Toggle"), Icon::Empty}, {Action::TransactionToggleCleared, &KMyMoneyApp::slotMarkTransactionCleared, i18nc("Mark transaction cleared", "Cleared"), Icon::Empty}, {Action::TransactionReconciled, &KMyMoneyApp::slotMarkTransactionReconciled, i18nc("Mark transaction reconciled", "Reconciled"), Icon::Empty}, {Action::TransactionNotReconciled, &KMyMoneyApp::slotMarkTransactionNotReconciled, i18nc("Mark transaction not reconciled", "Not reconciled"), Icon::Empty}, {Action::TransactionSelectAll, &KMyMoneyApp::selectAllTransactions, i18nc("Select all transactions", "Select all"), Icon::Empty}, {Action::TransactionGoToAccount, &KMyMoneyApp::slotTransactionGotoAccount, i18n("Go to account"), Icon::GoJump}, {Action::TransactionGoToPayee, &KMyMoneyApp::slotTransactionGotoPayee, i18n("Go to payee"), Icon::GoJump}, {Action::TransactionCreateSchedule, &KMyMoneyApp::slotTransactionCreateSchedule, i18n("Create scheduled transaction..."), Icon::AppointmentNew}, {Action::TransactionAssignNumber, &KMyMoneyApp::slotTransactionAssignNumber, i18n("Assign next number"), Icon::Empty}, {Action::TransactionCombine, &KMyMoneyApp::slotTransactionCombine, i18nc("Combine transactions", "Combine"), Icon::Empty}, {Action::TransactionCopySplits, &KMyMoneyApp::slotTransactionCopySplits, i18n("Copy splits"), Icon::Empty}, //Investment {Action::InvestmentNew, &KMyMoneyApp::slotInvestmentNew, i18n("New investment..."), Icon::Empty}, {Action::InvestmentEdit, &KMyMoneyApp::slotInvestmentEdit, i18n("Edit investment..."), Icon::Empty}, {Action::InvestmentDelete, &KMyMoneyApp::slotInvestmentDelete, i18n("Delete investment..."), Icon::Empty}, {Action::InvestmentOnlinePrice, &KMyMoneyApp::slotOnlinePriceUpdate, i18n("Online price update..."), Icon::Empty}, {Action::InvestmentManualPrice, &KMyMoneyApp::slotManualPriceUpdate, i18n("Manual price update..."), Icon::Empty}, //Schedule {Action::ScheduleNew, &KMyMoneyApp::slotScheduleNew, i18n("New scheduled transaction"), Icon::AppointmentNew}, {Action::ScheduleEdit, &KMyMoneyApp::slotScheduleEdit, i18n("Edit scheduled transaction"), Icon::DocumentEdit}, {Action::ScheduleDelete, &KMyMoneyApp::slotScheduleDelete, i18n("Delete scheduled transaction"), Icon::EditDelete}, {Action::ScheduleDuplicate, &KMyMoneyApp::slotScheduleDuplicate, i18n("Duplicate scheduled transaction"), Icon::EditCopy}, {Action::ScheduleEnter, &KMyMoneyApp::slotScheduleEnter, i18n("Enter next transaction..."), Icon::KeyEnter}, {Action::ScheduleSkip, &KMyMoneyApp::slotScheduleSkip, i18n("Skip next transaction..."), Icon::MediaSeekForward}, //Payees {Action::PayeeNew, &KMyMoneyApp::slotPayeeNew, i18n("New payee"), Icon::ListAddUser}, {Action::PayeeRename, &KMyMoneyApp::payeeRename, i18n("Rename payee"), Icon::PayeeRename}, {Action::PayeeDelete, &KMyMoneyApp::slotPayeeDelete, i18n("Delete payee"), Icon::ListRemoveUser}, {Action::PayeeMerge, &KMyMoneyApp::slotPayeeMerge, i18n("Merge payees"), Icon::PayeeMerge}, //Tags {Action::TagNew, &KMyMoneyApp::slotTagNew, i18n("New tag"), Icon::ListAddTag}, {Action::TagRename, &KMyMoneyApp::slotTagRename, i18n("Rename tag"), Icon::TagRename}, {Action::TagDelete, &KMyMoneyApp::slotTagDelete, i18n("Delete tag"), Icon::ListRemoveTag}, //Budget {Action::BudgetNew, &KMyMoneyApp::slotBudgetNew, i18n("New budget"), Icon::Empty}, {Action::BudgetRename, &KMyMoneyApp::budgetRename, i18n("Rename budget"), Icon::Empty}, {Action::BudgetDelete, &KMyMoneyApp::slotBudgetDelete, i18n("Delete budget"), Icon::Empty}, {Action::BudgetCopy, &KMyMoneyApp::slotBudgetCopy, i18n("Copy budget"), Icon::Empty}, {Action::BudgetChangeYear, &KMyMoneyApp::slotBudgetChangeYear, i18n("Change budget year"), Icon::ViewCalendar}, {Action::BudgetForecast, &KMyMoneyApp::slotBudgetForecast, i18n("Budget based on forecast"), Icon::ViewForecast}, //Currency actions {Action::CurrencyNew, &KMyMoneyApp::slotCurrencyNew, i18n("New currency"), Icon::Empty}, {Action::CurrencyRename, &KMyMoneyApp::currencyRename, i18n("Rename currency"), Icon::EditRename}, {Action::CurrencyDelete, &KMyMoneyApp::slotCurrencyDelete, i18n("Delete currency"), Icon::EditDelete}, {Action::CurrencySetBase, &KMyMoneyApp::slotCurrencySetBase, i18n("Select as base currency"), Icon::KMyMoney}, //Price actions {Action::PriceNew, &KMyMoneyApp::priceNew, i18n("New price..."), Icon::DocumentNew}, {Action::PriceEdit, &KMyMoneyApp::priceEdit, i18n("Edit price..."), Icon::DocumentEdit}, {Action::PriceUpdate, &KMyMoneyApp::priceOnlineUpdate, i18n("Online Price Update..."), Icon::Empty}, {Action::PriceDelete, &KMyMoneyApp::priceDelete, i18n("Delete price..."), Icon::EditDelete}, //debug actions #ifdef KMM_DEBUG {Action::WizardNewUser, &KMyMoneyApp::slotNewFeature, i18n("Test new feature"), Icon::Empty}, {Action::DebugTraces, &KMyMoneyApp::slotToggleTraces, i18n("Debug Traces"), Icon::Empty}, #endif {Action::DebugTimers, &KMyMoneyApp::slotToggleTimers, i18n("Debug Timers"), Icon::Empty}, // onlineJob actions {Action::OnlineJobDelete, &KMyMoneyApp::slotRemoveJob, i18n("Remove credit transfer"), Icon::EditDelete}, {Action::OnlineJobEdit, &KMyMoneyApp::slotEditJob, i18n("Edit credit transfer"), Icon::DocumentEdit}, {Action::OnlineJobLog, &KMyMoneyApp::slotOnlineJobLog, i18n("Show log"), Icon::Empty}, }; foreach (const auto info, actionInfos) { QAction *a = new QAction(0); // KActionCollection::addAction by name sets object name anyways, // so, as better alternative, set it here right from the start a->setObjectName(s_Actions.value(info.action)); a->setText(info.text); if (info.icon != Icon::Empty) // no need to set empty icon a->setIcon(QIcon::fromTheme(g_Icons.value(info.icon))); connect(a, &QAction::triggered, this, info.callback); lutActions.insert(info.action, a); // store QAction's pointer for later processing } } // ************* // Setting some of added actions checkable // ************* { // Some of acitions schould be checkable, // so set them here const QVector checkableActions { Action::ViewTransactionDetail, Action::ViewHideReconciled, Action::ViewHideCategories, #ifdef KMM_DEBUG Action::DebugTraces, #endif Action::ViewShowAll }; foreach (const auto it, checkableActions) lutActions[it]->setCheckable(true); } // ************* // Setting custom icons for some of added actions // ************* { // struct for QAction's icon overlays struct iconOverlayInfo { Action action; Icon icon; Icon overlay; Qt::Corner corner; }; const QVector actionOverlaidIcons { {Action::EditFindTransaction, Icon::ViewFinancialTransfer, Icon::EditFind, Qt::BottomRightCorner}, {Action::InstitutionNew, Icon::ViewBank, Icon::ListAdd, Qt::BottomRightCorner}, {Action::InstitutionEdit, Icon::ViewBank, Icon::DocumentEdit, Qt::BottomRightCorner}, {Action::InstitutionDelete, Icon::ViewBank, Icon::EditDelete, Qt::BottomRightCorner}, {Action::AccountNew, Icon::ViewBankAccount, Icon::ListAdd, Qt::TopRightCorner}, {Action::AccountFinishReconciliation, Icon::Merge, Icon::DialogOK, Qt::BottomRightCorner}, {Action::AccountEdit, Icon::ViewBankAccount, Icon::DocumentEdit, Qt::BottomRightCorner}, {Action::AccountDelete, Icon::ViewBankAccount, Icon::EditDelete, Qt::BottomRightCorner}, {Action::AccountClose, Icon::ViewBankAccount, Icon::DialogClose, Qt::BottomRightCorner}, {Action::AccountReopen, Icon::ViewBankAccount, Icon::DialogOK, Qt::BottomRightCorner}, {Action::AccountUpdateMenu, Icon::ViewBankAccount, Icon::Download, Qt::BottomRightCorner}, {Action::AccountUpdate, Icon::ViewBankAccount, Icon::Download, Qt::BottomRightCorner}, {Action::AccountUpdateAll, Icon::ViewBankAccount, Icon::Download, Qt::BottomRightCorner}, {Action::AccountCreditTransfer, Icon::ViewBankAccount, Icon::MailMessageNew, Qt::BottomRightCorner}, {Action::CategoryNew, Icon::ViewFinancialCategories, Icon::ListAdd, Qt::TopRightCorner}, {Action::CategoryEdit, Icon::ViewFinancialCategories, Icon::DocumentEdit, Qt::BottomRightCorner}, {Action::CategoryDelete, Icon::ViewFinancialCategories, Icon::EditDelete, Qt::BottomRightCorner}, {Action::ToolUpdatePrices, Icon::ViewInvestment, Icon::Download, Qt::BottomRightCorner}, {Action::TransactionNew, Icon::ViewFinancialTransfer, Icon::ListAdd, Qt::TopRightCorner}, {Action::TransactionEdit, Icon::ViewFinancialTransfer, Icon::DocumentEdit, Qt::BottomRightCorner}, {Action::TransactionMatch, Icon::ViewFinancialTransfer, Icon::DocumentImport, Qt::BottomRightCorner}, {Action::TransactionAccept, Icon::ViewFinancialTransfer, Icon::DialogOKApply, Qt::BottomRightCorner}, {Action::InvestmentNew, Icon::ViewInvestment, Icon::ListAdd, Qt::TopRightCorner}, {Action::InvestmentEdit, Icon::ViewInvestment, Icon::DocumentEdit, Qt::BottomRightCorner}, {Action::InvestmentDelete, Icon::ViewInvestment, Icon::EditDelete, Qt::BottomRightCorner}, {Action::InvestmentOnlinePrice, Icon::ViewInvestment, Icon::Download, Qt::BottomRightCorner}, {Action::BudgetNew, Icon::ViewTimeScheduleCalculus, Icon::ListAdd, Qt::TopRightCorner}, {Action::BudgetRename, Icon::ViewTimeScheduleCalculus, Icon::DocumentEdit, Qt::BottomRightCorner}, {Action::BudgetDelete, Icon::ViewTimeScheduleCalculus, Icon::EditDelete, Qt::BottomRightCorner}, {Action::BudgetCopy, Icon::ViewTimeScheduleCalculus, Icon::EditCopy, Qt::BottomRightCorner}, {Action::PriceUpdate, Icon::ViewCurrencyList, Icon::Download, Qt::BottomRightCorner} }; for(const auto& it : actionOverlaidIcons) lutActions[it.action]->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[it.icon], g_Icons[it.overlay], it.corner)); } // ************* // Setting keyboard shortcuts for some of added actions // ************* { const QVector> actionShortcuts { {qMakePair(Action::EditFindTransaction, Qt::CTRL + Qt::Key_F)}, {qMakePair(Action::ViewTransactionDetail, Qt::CTRL + Qt::Key_T)}, {qMakePair(Action::ViewHideReconciled, Qt::CTRL + Qt::Key_R)}, {qMakePair(Action::ViewHideCategories, Qt::CTRL + Qt::Key_U)}, {qMakePair(Action::ViewShowAll, Qt::CTRL + Qt::SHIFT + Qt::Key_A)}, {qMakePair(Action::AccountStartReconciliation, Qt::CTRL + Qt::SHIFT + Qt::Key_R)}, {qMakePair(Action::TransactionNew, Qt::CTRL + Qt::Key_Insert)}, {qMakePair(Action::TransactionToggleReconciled, Qt::CTRL + Qt::Key_Space)}, {qMakePair(Action::TransactionToggleCleared, Qt::CTRL + Qt::Key_Alt + Qt::Key_Space)}, {qMakePair(Action::TransactionReconciled, Qt::CTRL + Qt::Key_Shift + Qt::Key_Space)}, {qMakePair(Action::TransactionSelectAll, Qt::CTRL + Qt::Key_A)}, #ifdef KMM_DEBUG {qMakePair(Action::WizardNewUser, Qt::CTRL + Qt::Key_G)}, #endif {qMakePair(Action::TransactionAssignNumber, Qt::CTRL + Qt::Key_Shift + Qt::Key_N)} }; for(const auto& it : actionShortcuts) aC->setDefaultShortcut(lutActions[it.first], it.second); } // ************* // Misc settings // ************* connect(onlineJobAdministration::instance(), &onlineJobAdministration::canSendCreditTransferChanged, lutActions.value(Action::AccountCreditTransfer), &QAction::setEnabled); // Setup transaction detail switch lutActions[Action::ViewTransactionDetail]->setChecked(KMyMoneyGlobalSettings::showRegisterDetailed()); lutActions[Action::ViewHideReconciled]->setChecked(KMyMoneyGlobalSettings::hideReconciledTransactions()); lutActions[Action::ViewHideCategories]->setChecked(KMyMoneyGlobalSettings::hideUnusedCategory()); lutActions[Action::ViewShowAll]->setChecked(false); // ************* // Adding actions to ActionCollection // ************* actionCollection()->addActions(lutActions.values()); // ************************ // Currently unused actions // ************************ #if 0 new KToolBarPopupAction(i18n("View back"), "go-previous", 0, this, SLOT(slotShowPreviousView()), actionCollection(), "go_back"); new KToolBarPopupAction(i18n("View forward"), "go-next", 0, this, SLOT(slotShowNextView()), actionCollection(), "go_forward"); action("go_back")->setEnabled(false); action("go_forward")->setEnabled(false); #endif // use the absolute path to your kmymoneyui.rc file for testing purpose in createGUI(); setupGUI(); // reconnect about app entry to dialog with full credits information QAction *aboutApp = aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::AboutApp))); aboutApp->disconnect(); connect(aboutApp, &QAction::triggered, this, &KMyMoneyApp::slotShowCredits); QMenu *menuContainer; menuContainer = static_cast(factory()->container(QStringLiteral("import"), this)); menuContainer->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentImport])); menuContainer = static_cast(factory()->container(QStringLiteral("export"), this)); menuContainer->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentExport])); } void KMyMoneyApp::connectActionsAndViews() { KOnlineJobOutbox *const outbox = d->m_myMoneyView->getOnlineJobOutbox(); Q_CHECK_PTR(outbox); QAction *const onlineJob_delete = actionCollection()->action(s_Actions[Action::OnlineJobDelete]); Q_CHECK_PTR(onlineJob_delete); connect(onlineJob_delete, SIGNAL(triggered()), outbox, SLOT(slotRemoveJob())); QAction *const onlineJob_edit = actionCollection()->action(s_Actions[Action::OnlineJobEdit]); Q_CHECK_PTR(onlineJob_edit); connect(onlineJob_edit, SIGNAL(triggered()), outbox, SLOT(slotEditJob())); } #ifdef KMM_DEBUG void KMyMoneyApp::dumpActions() const { const QList list = actionCollection()->actions(); foreach (const auto it, list) std::cout << qPrintable(it->objectName()) << ": " << qPrintable(it->text()) << std::endl; } #endif bool KMyMoneyApp::isActionToggled(const Action _a) { return actionCollection()->action(s_Actions[_a])->isChecked(); } void KMyMoneyApp::initStatusBar() { /////////////////////////////////////////////////////////////////// // STATUSBAR d->m_statusLabel = new QLabel; statusBar()->addWidget(d->m_statusLabel); ready(); // Initialization of progress bar taken from KDevelop ;-) d->m_progressBar = new QProgressBar; statusBar()->addWidget(d->m_progressBar); d->m_progressBar->setFixedHeight(d->m_progressBar->sizeHint().height() - 8); // hide the progress bar for now slotStatusProgressBar(-1, -1); } void KMyMoneyApp::saveOptions() { KConfigGroup grp = d->m_config->group("General Options"); grp.writeEntry("Geometry", size()); grp.writeEntry("Show Statusbar", actionCollection()->action(KStandardAction::name(KStandardAction::ShowStatusbar))->isChecked()); KConfigGroup toolbarGrp = d->m_config->group("mainToolBar"); toolBar("mainToolBar")->saveSettings(toolbarGrp); d->m_recentFiles->saveEntries(d->m_config->group("Recent Files")); } void KMyMoneyApp::readOptions() { KConfigGroup grp = d->m_config->group("General Options"); actionCollection()->action(s_Actions[Action::ViewHideReconciled])->setChecked(KMyMoneyGlobalSettings::hideReconciledTransactions()); actionCollection()->action(s_Actions[Action::ViewHideCategories])->setChecked(KMyMoneyGlobalSettings::hideUnusedCategory()); d->m_recentFiles->loadEntries(d->m_config->group("Recent Files")); // Startdialog is written in the settings dialog d->m_startDialog = grp.readEntry("StartDialog", true); } void KMyMoneyApp::resizeEvent(QResizeEvent* ev) { KMainWindow::resizeEvent(ev); updateCaption(true); } int KMyMoneyApp::askSaveOnClose() { int ans; if (KMyMoneyGlobalSettings::autoSaveOnClose()) { ans = KMessageBox::Yes; } else { ans = KMessageBox::warningYesNoCancel(this, i18n("The file has been changed, save it?")); } return ans; } bool KMyMoneyApp::queryClose() { if (!isReady()) return false; if (d->m_myMoneyView->dirty()) { int ans = askSaveOnClose(); if (ans == KMessageBox::Cancel) return false; else if (ans == KMessageBox::Yes) { bool saved = slotFileSave(); saveOptions(); return saved; } } if (d->m_myMoneyView->isDatabase()) slotFileClose(); // close off the database saveOptions(); return true; } ///////////////////////////////////////////////////////////////////// // SLOT IMPLEMENTATION ///////////////////////////////////////////////////////////////////// void KMyMoneyApp::slotFileInfoDialog() { QPointer dlg = new KMyMoneyFileInfoDlg(0); dlg->exec(); delete dlg; } void KMyMoneyApp::slotPerformanceTest() { // dump performance report to stderr int measurement[2]; QTime timer; MyMoneyAccount acc; qDebug("--- Starting performance tests ---"); // AccountList MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; timer.start(); for (int i = 0; i < 1000; ++i) { QList list; MyMoneyFile::instance()->accountList(list); measurement[i != 0] = timer.elapsed(); } std::cerr << "accountList()" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // Balance of asset account(s) MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->asset(); for (int i = 0; i < 1000; ++i) { timer.start(); MyMoneyMoney result = MyMoneyFile::instance()->balance(acc.id()); measurement[i != 0] += timer.elapsed(); } std::cerr << "balance(Asset)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // total balance of asset account MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->asset(); for (int i = 0; i < 1000; ++i) { timer.start(); MyMoneyMoney result = MyMoneyFile::instance()->totalBalance(acc.id()); measurement[i != 0] += timer.elapsed(); } std::cerr << "totalBalance(Asset)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // Balance of expense account(s) MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->expense(); for (int i = 0; i < 1000; ++i) { timer.start(); MyMoneyMoney result = MyMoneyFile::instance()->balance(acc.id()); measurement[i != 0] += timer.elapsed(); } std::cerr << "balance(Expense)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // total balance of expense account MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->expense(); timer.start(); for (int i = 0; i < 1000; ++i) { MyMoneyMoney result = MyMoneyFile::instance()->totalBalance(acc.id()); measurement[i != 0] = timer.elapsed(); } std::cerr << "totalBalance(Expense)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // transaction list MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; if (MyMoneyFile::instance()->asset().accountCount()) { MyMoneyTransactionFilter filter(MyMoneyFile::instance()->asset().accountList()[0]); filter.setDateFilter(QDate(), QDate::currentDate()); QList list; timer.start(); for (int i = 0; i < 100; ++i) { list = MyMoneyFile::instance()->transactionList(filter); measurement[i != 0] = timer.elapsed(); } std::cerr << "transactionList()" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 100 << " msec" << std::endl; } // transaction list MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; if (MyMoneyFile::instance()->asset().accountCount()) { MyMoneyTransactionFilter filter(MyMoneyFile::instance()->asset().accountList()[0]); filter.setDateFilter(QDate(), QDate::currentDate()); QList list; timer.start(); for (int i = 0; i < 100; ++i) { MyMoneyFile::instance()->transactionList(list, filter); measurement[i != 0] = timer.elapsed(); } std::cerr << "transactionList(list)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 100 << " msec" << std::endl; } MyMoneyFile::instance()->preloadCache(); } void KMyMoneyApp::slotFileNew() { KMSTATUS(i18n("Creating new document...")); slotFileClose(); if (!d->m_myMoneyView->fileOpen()) { // next line required until we move all file handling out of KMyMoneyView d->m_myMoneyView->newFile(); d->m_fileName = QUrl(); updateCaption(); NewUserWizard::Wizard *wizard = new NewUserWizard::Wizard(); if (wizard->exec() == QDialog::Accepted) { MyMoneyFileTransaction ft; MyMoneyFile* file = MyMoneyFile::instance(); try { // store the user info file->setUser(wizard->user()); // create and setup base currency file->addCurrency(wizard->baseCurrency()); file->setBaseCurrency(wizard->baseCurrency()); // create a possible institution MyMoneyInstitution inst = wizard->institution(); if (inst.name().length()) { file->addInstitution(inst); } // create a possible checking account MyMoneyAccount acc = wizard->account(); if (acc.name().length()) { acc.setInstitutionId(inst.id()); MyMoneyAccount asset = file->asset(); file->addAccount(acc, asset); // create possible opening balance transaction if (!wizard->openingBalance().isZero()) { file->createOpeningBalanceTransaction(acc, wizard->openingBalance()); } } // import the account templates QList templates = wizard->templates(); QList::iterator it_t; for (it_t = templates.begin(); it_t != templates.end(); ++it_t) { (*it_t).importTemplate(&progressCallback); } d->m_fileName = wizard->url(); ft.commit(); KMyMoneyGlobalSettings::setFirstTimeRun(false); // FIXME This is a bit clumsy. We re-read the freshly // created file to be able to run through all the // fixup logic and then save it to keep the modified // flag off. slotFileSave(); d->m_myMoneyView->readFile(d->m_fileName); slotFileSave(); // now keep the filename in the recent files used list //KRecentFilesAction *p = dynamic_cast(action(KStandardAction::name(KStandardAction::OpenRecent))); //if(p) d->m_recentFiles->addUrl(d->m_fileName); writeLastUsedFile(d->m_fileName.url()); } catch (const MyMoneyException &) { // next line required until we move all file handling out of KMyMoneyView d->m_myMoneyView->closeFile(); } if (wizard->startSettingsAfterFinished()) slotSettings(); } else { // next line required until we move all file handling out of KMyMoneyView d->m_myMoneyView->closeFile(); } delete wizard; updateCaption(); emit fileLoaded(d->m_fileName); } } QUrl KMyMoneyApp::selectFile(const QString& title, const QString& _path, const QString& mask, QFileDialog::FileMode mode, QWidget* widget) { QString path(_path); // if the path is not specified open the file dialog in the last used directory // 'kmymoney' is the keyword that identifies the last used directory in KFileDialog if (path.isEmpty()) { path = KRecentDirs::dir(":kmymoney-import"); } QPointer dialog = new QFileDialog(this, title, path, mask); dialog->setFileMode(mode); QUrl url; if (dialog->exec() == QDialog::Accepted && dialog != 0) { QList selectedUrls = dialog->selectedUrls(); if (!selectedUrls.isEmpty()) { url = selectedUrls.first(); if (_path.isEmpty()) { KRecentDirs::add(":kmymoney-import", url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path()); } } } // in case we have an additional widget, we remove it from the // dialog, so that the caller can still access it. Therefore, it is // the callers responsibility to delete the object if (widget) widget->setParent(0); delete dialog; return url; } // General open void KMyMoneyApp::slotFileOpen() { KMSTATUS(i18n("Open a file.")); QString prevDir = readLastUsedDir(); QPointer dialog = new QFileDialog(this, QString(), prevDir, i18n("KMyMoney files (*.kmy *.xml);;All files")); dialog->setFileMode(QFileDialog::ExistingFile); dialog->setAcceptMode(QFileDialog::AcceptOpen); if (dialog->exec() == QDialog::Accepted && dialog != nullptr) { slotFileOpenRecent(dialog->selectedUrls().first()); } delete dialog; } void KMyMoneyApp::slotOpenDatabase() { KMSTATUS(i18n("Open a file.")); QPointer dialog = new KSelectDatabaseDlg(QIODevice::ReadWrite); if (!dialog->checkDrivers()) { delete dialog; return; } if (dialog->exec() == QDialog::Accepted && dialog != 0) { slotFileOpenRecent(dialog->selectedURL()); } delete dialog; } bool KMyMoneyApp::isImportableFile(const QUrl &url) { bool result = false; // Iterate through the plugins and see if there's a loaded plugin who can handle it QMap::const_iterator it_plugin = d->m_importerPlugins.constBegin(); while (it_plugin != d->m_importerPlugins.constEnd()) { if ((*it_plugin)->isMyFormat(url.path())) { result = true; break; } ++it_plugin; } // If we did not find a match, try importing it as a KMM statement file, // which is really just for testing. the statement file is not exposed // to users. if (it_plugin == d->m_importerPlugins.constEnd()) if (MyMoneyStatement::isStatementFile(url.path())) result = true; // Place code here to test for QIF and other locally-supported formats // (i.e. not a plugin). If you add them here, be sure to add it to // the webConnect function. return result; } void KMyMoneyApp::slotFileOpenRecent(const QUrl &url) { KMSTATUS(i18n("Loading file...")); QUrl lastFile = d->m_fileName; // check if there are other instances which might have this file open QList list = instanceList(); QList::ConstIterator it; bool duplicate = false; #ifdef KMM_DBUS for (it = list.constBegin(); duplicate == false && it != list.constEnd(); ++it) { QDBusInterface remoteApp(*it, "/KMymoney", "org.kde.kmymoney"); QDBusReply reply = remoteApp.call("filename"); if (!reply.isValid()) { qDebug("D-Bus error while calling app->filename()"); } else { if (reply.value() == url.url()) { duplicate = true; } } } #endif if (!duplicate) { QUrl newurl = url; if ((newurl.scheme() == QLatin1String("sql"))) { const QString key = QLatin1String("driver"); // take care and convert some old url to their new counterpart QUrlQuery query(newurl); if (query.queryItemValue(key) == QLatin1String("QMYSQL3")) { // fix any old urls query.removeQueryItem(key); query.addQueryItem(key, QLatin1String("QMYSQL")); } if (query.queryItemValue(key) == QLatin1String("QSQLITE3")) { query.removeQueryItem(key); query.addQueryItem(key, QLatin1String("QSQLITE")); } newurl.setQuery(query); if (query.queryItemValue(key) == QLatin1String("QSQLITE")) { newurl.setUserInfo(QString()); newurl.setHost(QString()); } // check if a password is needed. it may be if the URL came from the last/recent file list QPointer dialog = new KSelectDatabaseDlg(QIODevice::ReadWrite, newurl); if (!dialog->checkDrivers()) { delete dialog; return; } // if we need to supply a password, then show the dialog // otherwise it isn't needed if ((query.queryItemValue("secure").toLower() == QLatin1String("yes")) && newurl.password().isEmpty()) { if (dialog->exec() == QDialog::Accepted && dialog != nullptr) { newurl = dialog->selectedURL(); } else { delete dialog; return; } } delete dialog; } // TODO: port KF5 (NetAccess) if ((newurl.scheme() == QLatin1String("sql")) || (newurl.isValid() /*&& KIO::NetAccess::exists(newurl, KIO::NetAccess::SourceSide, this)*/)) { slotFileClose(); if (!d->m_myMoneyView->fileOpen()) { try { if (d->m_myMoneyView->readFile(newurl)) { if ((d->m_myMoneyView->isNativeFile())) { d->m_fileName = newurl; updateCaption(); d->m_recentFiles->addUrl(newurl); writeLastUsedFile(newurl.toDisplayString(QUrl::PreferLocalFile)); } else { d->m_fileName = QUrl(); // imported files have no filename } // Check the schedules slotCheckSchedules(); } } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot open file as requested. Error was: %1", e.what())); } updateCaption(); emit fileLoaded(d->m_fileName); } else { /*fileOpen failed - should we do something or maybe fileOpen puts out the message... - it does for database*/ } } else { // newurl invalid slotFileClose(); KMessageBox::sorry(this, i18n("

%1 is either an invalid filename or the file does not exist. You can open another file or create a new one.

", url.toDisplayString(QUrl::PreferLocalFile)), i18n("File not found")); } } else { // isDuplicate KMessageBox::sorry(this, i18n("

File %1 is already opened in another instance of KMyMoney

", url.toDisplayString(QUrl::PreferLocalFile)), i18n("Duplicate open")); } } bool KMyMoneyApp::slotFileSave() { // if there's nothing changed, there's no need to save anything if (!d->m_myMoneyView->dirty()) return true; bool rc = false; KMSTATUS(i18n("Saving file...")); if (d->m_fileName.isEmpty()) return slotFileSaveAs(); d->consistencyCheck(false); /*if (myMoneyView->isDatabase()) { rc = myMoneyView->saveDatabase(m_fileName); // the 'save' function is no longer relevant for a database*/ setEnabled(false); rc = d->m_myMoneyView->saveFile(d->m_fileName, MyMoneyFile::instance()->value("kmm-encryption-key")); setEnabled(true); d->m_autoSaveTimer->stop(); updateCaption(); return rc; } void KMyMoneyApp::slotFileSaveAsFilterChanged(const QString& filter) { if (!d->m_saveEncrypted) return; if (filter.compare(QLatin1String("*.kmy"), Qt::CaseInsensitive) != 0) { d->m_saveEncrypted->setCurrentItem(0); d->m_saveEncrypted->setEnabled(false); } else { d->m_saveEncrypted->setEnabled(true); } } void KMyMoneyApp::slotManageGpgKeys() { QPointer dlg = new KGpgKeySelectionDlg(this); dlg->setKeys(d->m_additionalGpgKeys); if (dlg->exec() == QDialog::Accepted && dlg != 0) { d->m_additionalGpgKeys = dlg->keys(); d->m_additionalKeyLabel->setText(i18n("Additional encryption keys to be used: %1", d->m_additionalGpgKeys.count())); } delete dlg; } void KMyMoneyApp::slotKeySelected(int idx) { int cnt = 0; if (idx != 0) { cnt = d->m_additionalGpgKeys.count(); } d->m_additionalKeyLabel->setEnabled(idx != 0); d->m_additionalKeyButton->setEnabled(idx != 0); d->m_additionalKeyLabel->setText(i18n("Additional encryption keys to be used: %1", cnt)); } bool KMyMoneyApp::slotFileSaveAs() { bool rc = false; // in event of it being a database, ensure that all data is read into storage for saveas if (d->m_myMoneyView->isDatabase()) dynamic_cast(MyMoneyFile::instance()->storage())->fillStorage(); KMSTATUS(i18n("Saving file with a new filename...")); // fill the additional key list with the default d->m_additionalGpgKeys = KMyMoneyGlobalSettings::gpgRecipientList(); QWidget* vbox = new QWidget(); QVBoxLayout *vboxVBoxLayout = new QVBoxLayout(vbox); vboxVBoxLayout->setMargin(0); if (KGPGFile::GPGAvailable()) { QWidget* keyBox = new QWidget(vbox); QHBoxLayout *keyBoxHBoxLayout = new QHBoxLayout(keyBox); keyBoxHBoxLayout->setMargin(0); vboxVBoxLayout->addWidget(keyBox); QLabel *keyLabel = new QLabel(i18n("Encryption key to be used"), keyBox); keyBoxHBoxLayout->addWidget(keyLabel); d->m_saveEncrypted = new KComboBox(keyBox); keyBoxHBoxLayout->addWidget(d->m_saveEncrypted); QWidget* labelBox = new QWidget(vbox); QHBoxLayout *labelBoxHBoxLayout = new QHBoxLayout(labelBox); labelBoxHBoxLayout->setMargin(0); vboxVBoxLayout->addWidget(labelBox); d->m_additionalKeyLabel = new QLabel(i18n("Additional encryption keys to be used: %1", d->m_additionalGpgKeys.count()), labelBox); labelBoxHBoxLayout->addWidget(d->m_additionalKeyLabel); d->m_additionalKeyButton = new QPushButton(i18n("Manage additional keys"), labelBox); labelBoxHBoxLayout->addWidget(d->m_additionalKeyButton); connect(d->m_additionalKeyButton, SIGNAL(clicked()), this, SLOT(slotManageGpgKeys())); connect(d->m_saveEncrypted, SIGNAL(activated(int)), this, SLOT(slotKeySelected(int))); // fill the secret key list and combo box QStringList keyList; KGPGFile::secretKeyList(keyList); d->m_saveEncrypted->addItem(i18n("No encryption")); for (QStringList::iterator it = keyList.begin(); it != keyList.end(); ++it) { QStringList fields = (*it).split(':', QString::SkipEmptyParts); if (fields[0] != recoveryKeyId) { // replace parenthesis in name field with brackets QString name = fields[1]; name.replace('(', "["); name.replace(')', "]"); name = QString("%1 (0x%2)").arg(name).arg(fields[0]); d->m_saveEncrypted->addItem(name); if (name.contains(KMyMoneyGlobalSettings::gpgRecipient())) { d->m_saveEncrypted->setCurrentItem(name); } } } } QString prevDir; // don't prompt file name if not a native file if (d->m_myMoneyView->isNativeFile()) prevDir = readLastUsedDir(); QPointer dlg = new QFileDialog(this, i18n("Save As"), prevDir, QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.kmy")).arg(i18nc("KMyMoney (Filefilter)", "KMyMoney files")) + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.xml")).arg(i18nc("XML (Filefilter)", "XML files")) + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.anon.xml")).arg(i18nc("Anonymous (Filefilter)", "Anonymous files")) + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*")).arg(i18nc("All files (Filefilter)", "All files"))); dlg->setAcceptMode(QFileDialog::AcceptSave); connect(dlg, SIGNAL(filterChanged(QString)), this, SLOT(slotFileSaveAsFilterChanged(QString))); if (dlg->exec() == QDialog::Accepted && dlg != 0) { QUrl newURL = dlg->selectedUrls().first(); if (!newURL.fileName().isEmpty()) { d->consistencyCheck(false); // deleting the dialog will delete the combobox pointed to by d->m_saveEncrypted so get the key name here QString selectedKeyName; if (d->m_saveEncrypted && d->m_saveEncrypted->currentIndex() != 0) selectedKeyName = d->m_saveEncrypted->currentText(); d->m_saveEncrypted = 0; QString newName = newURL.toDisplayString(QUrl::PreferLocalFile); // append extension if not present if (!newName.endsWith(QLatin1String(".kmy"), Qt::CaseInsensitive) && !newName.endsWith(QLatin1String(".xml"), Qt::CaseInsensitive)) newName.append(QLatin1String(".kmy")); newURL = QUrl::fromUserInput(newName); d->m_recentFiles->addUrl(newURL); setEnabled(false); // If this is the anonymous file export, just save it, don't actually take the // name, or remember it! Don't even try to encrypt it if (newName.endsWith(QLatin1String(".anon.xml"), Qt::CaseInsensitive)) rc = d->m_myMoneyView->saveFile(newURL); else { d->m_fileName = newURL; QString encryptionKeys; QRegExp keyExp(".* \\((.*)\\)"); if (keyExp.indexIn(selectedKeyName) != -1) { encryptionKeys = keyExp.cap(1); } if (!d->m_additionalGpgKeys.isEmpty()) { if (!encryptionKeys.isEmpty()) encryptionKeys.append(QLatin1Char(',')); encryptionKeys.append(d->m_additionalGpgKeys.join(QLatin1Char(','))); } rc = d->m_myMoneyView->saveFile(d->m_fileName, encryptionKeys); //write the directory used for this file as the default one for next time. writeLastUsedDir(newURL.toDisplayString(QUrl::RemoveFilename | QUrl::PreferLocalFile | QUrl::StripTrailingSlash)); writeLastUsedFile(newName); } d->m_autoSaveTimer->stop(); setEnabled(true); } } delete dlg; updateCaption(); return rc; } void KMyMoneyApp::slotSaveAsDatabase() { saveAsDatabase(); } bool KMyMoneyApp::saveAsDatabase() { bool rc = false; QUrl oldUrl; // in event of it being a database, ensure that all data is read into storage for saveas if (d->m_myMoneyView->isDatabase()) { dynamic_cast(MyMoneyFile::instance()->storage())->fillStorage(); oldUrl = d->m_fileName.isEmpty() ? lastOpenedURL() : d->m_fileName; } KMSTATUS(i18n("Saving file to database...")); QPointer dialog = new KSelectDatabaseDlg(QIODevice::WriteOnly); QUrl url = oldUrl; if (!dialog->checkDrivers()) { delete dialog; return (false); } while (oldUrl == url && dialog->exec() == QDialog::Accepted && dialog != 0) { url = dialog->selectedURL(); // If the protocol is SQL for the old and new, and the hostname and database names match // Let the user know that the current database cannot be saved on top of itself. if (url.scheme() == "sql" && oldUrl.scheme() == "sql" && oldUrl.host() == url.host() && QUrlQuery(oldUrl).queryItemValue("driver") == QUrlQuery(url).queryItemValue("driver") && oldUrl.path().right(oldUrl.path().length() - 1) == url.path().right(url.path().length() - 1)) { KMessageBox::sorry(this, i18n("Cannot save to current database.")); } else { try { rc = d->m_myMoneyView->saveAsDatabase(url); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot save to current database: %1", e.what())); } } } delete dialog; if (rc) { //KRecentFilesAction *p = dynamic_cast(action("file_open_recent")); //if(p) d->m_recentFiles->addUrl(url); writeLastUsedFile(url.toDisplayString(QUrl::PreferLocalFile)); } d->m_autoSaveTimer->stop(); updateCaption(); return rc; } void KMyMoneyApp::slotFileCloseWindow() { KMSTATUS(i18n("Closing window...")); if (d->m_myMoneyView->dirty()) { int answer = askSaveOnClose(); if (answer == KMessageBox::Cancel) return; else if (answer == KMessageBox::Yes) slotFileSave(); } close(); } void KMyMoneyApp::slotFileClose() { bool okToSelect = true; // check if transaction editor is open and ask user what he wants to do slotTransactionsCancelOrEnter(okToSelect); if (!okToSelect) return; // no update status here, as we might delete the status too early. if (d->m_myMoneyView->dirty()) { int answer = askSaveOnClose(); if (answer == KMessageBox::Cancel) return; else if (answer == KMessageBox::Yes) slotFileSave(); } d->closeFile(); } void KMyMoneyApp::slotFileQuit() { // don't modify the status message here as this will prevent quit from working!! // See the beginning of queryClose() and isReady() why. Thomas Baumgart 2005-10-17 bool quitApplication = true; QList memberList = KMainWindow::memberList(); if (!memberList.isEmpty()) { QList::const_iterator w_it = memberList.constBegin(); for (; w_it != memberList.constEnd(); ++w_it) { // only close the window if the closeEvent is accepted. If the user presses Cancel on the saveModified() dialog, // the window and the application stay open. if (!(*w_it)->close()) { quitApplication = false; break; } } } // We will only quit if all windows were processed and not cancelled if (quitApplication) { QCoreApplication::quit(); } } void KMyMoneyApp::slotShowTransactionDetail() { } void KMyMoneyApp::slotHideReconciledTransactions() { KMyMoneyGlobalSettings::setHideReconciledTransactions(actionCollection()->action(s_Actions[Action::ViewHideReconciled])->isChecked()); d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::slotHideUnusedCategories() { KMyMoneyGlobalSettings::setHideUnusedCategory(actionCollection()->action(s_Actions[Action::ViewHideCategories])->isChecked()); d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::slotShowAllAccounts() { d->m_myMoneyView->slotRefreshViews(); } #ifdef KMM_DEBUG void KMyMoneyApp::slotToggleTraces() { MyMoneyTracer::onOff(actionCollection()->action(s_Actions[Action::DebugTraces])->isChecked() ? 1 : 0); } #endif void KMyMoneyApp::slotToggleTimers() { extern bool timersOn; // main.cpp timersOn = actionCollection()->action(s_Actions[Action::DebugTimers])->isChecked(); } QString KMyMoneyApp::slotStatusMsg(const QString &text) { /////////////////////////////////////////////////////////////////// // change status message permanently QString previousMessage = d->m_statusLabel->text(); d->m_applicationIsReady = false; QString currentMessage = text; if (currentMessage.isEmpty() || currentMessage == i18nc("Application is ready to use", "Ready.")) { d->m_applicationIsReady = true; currentMessage = i18nc("Application is ready to use", "Ready."); } statusBar()->clearMessage(); d->m_statusLabel->setText(currentMessage); return previousMessage; } void KMyMoneyApp::ready() { slotStatusMsg(QString()); } bool KMyMoneyApp::isReady() { return d->m_applicationIsReady; } void KMyMoneyApp::slotStatusProgressBar(int current, int total) { if (total == -1 && current == -1) { // reset if (d->m_progressTimer) { d->m_progressTimer->start(500); // remove from screen in 500 msec d->m_progressBar->setValue(d->m_progressBar->maximum()); } } else if (total != 0) { // init d->m_progressTimer->stop(); d->m_progressBar->setMaximum(total); d->m_progressBar->setValue(0); d->m_progressBar->show(); } else { // update QTime currentTime = QTime::currentTime(); // only process painting if last update is at least 250 ms ago if (abs(d->m_lastUpdate.msecsTo(currentTime)) > 250) { d->m_progressBar->setValue(current); d->m_lastUpdate = currentTime; } } } void KMyMoneyApp::slotStatusProgressDone() { d->m_progressTimer->stop(); d->m_progressBar->reset(); d->m_progressBar->hide(); d->m_progressBar->setValue(0); } void KMyMoneyApp::progressCallback(int current, int total, const QString& msg) { if (!msg.isEmpty()) kmymoney->slotStatusMsg(msg); kmymoney->slotStatusProgressBar(current, total); } void KMyMoneyApp::slotFileViewPersonal() { if (!d->m_myMoneyView->fileOpen()) { KMessageBox::information(this, i18n("No KMyMoneyFile open")); return; } KMSTATUS(i18n("Viewing personal data...")); MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyPayee user = file->user(); QPointer editPersonalDataDlg = new EditPersonalDataDlg(user.name(), user.address(), user.city(), user.state(), user.postcode(), user.telephone(), user.email(), this, i18n("Edit Personal Data")); if (editPersonalDataDlg->exec() == QDialog::Accepted && editPersonalDataDlg != 0) { user.setName(editPersonalDataDlg->userName()); user.setAddress(editPersonalDataDlg->userStreet()); user.setCity(editPersonalDataDlg->userTown()); user.setState(editPersonalDataDlg->userCountry()); user.setPostcode(editPersonalDataDlg->userPostcode()); user.setTelephone(editPersonalDataDlg->userTelephone()); user.setEmail(editPersonalDataDlg->userEmail()); MyMoneyFileTransaction ft; try { file->setUser(user); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to store user information: %1", e.what())); } } delete editPersonalDataDlg; } void KMyMoneyApp::slotFileFileInfo() { if (!d->m_myMoneyView->fileOpen()) { KMessageBox::information(this, i18n("No KMyMoneyFile open")); return; } QFile g("kmymoney.dump"); g.open(QIODevice::WriteOnly); QDataStream st(&g); MyMoneyStorageDump dumper; dumper.writeStream(st, dynamic_cast(MyMoneyFile::instance()->storage())); g.close(); } void KMyMoneyApp::slotLoadAccountTemplates() { KMSTATUS(i18n("Importing account templates.")); int rc; QPointer dlg = new KLoadTemplateDlg(); if ((rc = dlg->exec()) == QDialog::Accepted && dlg != 0) { MyMoneyFileTransaction ft; try { // import the account templates QList templates = dlg->templates(); QList::iterator it_t; for (it_t = templates.begin(); it_t != templates.end(); ++it_t) { (*it_t).importTemplate(&progressCallback); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to import template(s): %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } delete dlg; } void KMyMoneyApp::slotSaveAccountTemplates() { KMSTATUS(i18n("Exporting account templates.")); QString savePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/templates/" + QLocale().name(); QDir d(savePath); if (!d.exists()) d.mkpath(savePath); QString newName = QFileDialog::getSaveFileName(this, i18n("Save as..."), savePath, i18n("KMyMoney template files (*.kmt);;All files (*)")); // // If there is no file extension, then append a .kmt at the end of the file name. // If there is a file extension, make sure it is .kmt, delete any others. // if (!newName.isEmpty()) { // find last . delimiter int nLoc = newName.lastIndexOf('.'); if (nLoc != -1) { QString strExt, strTemp; strTemp = newName.left(nLoc + 1); strExt = newName.right(newName.length() - (nLoc + 1)); if ((strExt.indexOf("kmt", 0, Qt::CaseInsensitive) == -1)) { strTemp.append("kmt"); //append to make complete file name newName = strTemp; } } else { newName.append(".kmt"); } if (okToWriteFile(QUrl::fromLocalFile(newName))) { QPointer dlg = new KTemplateExportDlg(this); if (dlg->exec() == QDialog::Accepted && dlg) { MyMoneyTemplate templ; templ.setTitle(dlg->title()); templ.setShortDescription(dlg->shortDescription()); templ.setLongDescription(dlg->longDescription()); templ.exportTemplate(&progressCallback); templ.saveTemplate(QUrl::fromLocalFile(newName)); } delete dlg; } } } void KMyMoneyApp::slotGncImport() { if (d->m_myMoneyView->fileOpen()) { switch (KMessageBox::questionYesNoCancel(0, i18n("You cannot import GnuCash data into an existing file. Do you wish to save this file?"), PACKAGE)) { case KMessageBox::Yes: slotFileSave(); break; case KMessageBox::No: d->closeFile(); break; default: return; } } KMSTATUS(i18n("Importing a GnuCash file.")); QUrl fileToRead = QFileDialog::getOpenFileUrl(this, QString(), QUrl(), i18n("GnuCash files (*.gnucash *.xac *.gnc);;All files (*)")); if (!fileToRead.isEmpty()) { // call the importer if (d->m_myMoneyView->readFile(fileToRead)) { // imported files don't have a name d->m_fileName = QUrl(); updateCaption(); emit fileLoaded(d->m_fileName); } } } void KMyMoneyApp::slotAccountChart() { if (!d->m_selectedAccount.id().isEmpty()) { QPointer dlg = new KBalanceChartDlg(d->m_selectedAccount, this); dlg->exec(); delete dlg; } } // // KMyMoneyApp::slotStatementImport() is for testing only. The MyMoneyStatement // is not intended to be exposed to users in XML form. // void KMyMoneyApp::slotStatementImport() { bool result = false; KMSTATUS(i18n("Importing an XML Statement.")); QList files{QFileDialog::getOpenFileUrls(this, QString(), QUrl(), i18n("XML files (*.xml);;All files (*)"))}; if (!files.isEmpty()) { d->m_collectingStatements = (files.count() > 1); foreach (const QUrl &url, files) { qDebug("Processing '%s'", qPrintable(url.path())); result |= slotStatementImport(url.path()); } /* QFile f( dialog->selectedURL().path() ); f.open( QIODevice::ReadOnly ); QString error = "Unable to parse file"; QDomDocument* doc = new QDomDocument; if(doc->setContent(&f, FALSE)) { if ( doc->doctype().name() == "KMYMONEY-STATEMENT" ) { QDomElement rootElement = doc->documentElement(); if(!rootElement.isNull()) { QDomNode child = rootElement.firstChild(); if(!child.isNull() && child.isElement()) { MyMoneyStatement s; if ( s.read(child.toElement()) ) result = slotStatementImport(s); else error = "File does not contain any statements"; } } } else error = "File is not a KMyMoney Statement"; } delete doc; if ( !result ) { QMessageBox::critical( this, i18n("Critical Error"), i18n("Unable to read file %1: %2").arg( dialog->selectedURL().path()).arg(error), QMessageBox::Ok, 0 ); }*/ } if (!result) { // re-enable all standard widgets setEnabled(true); } } bool KMyMoneyApp::slotStatementImport(const QString& url) { bool result = false; MyMoneyStatement s; if (MyMoneyStatement::readXMLFile(s, url)) result = slotStatementImport(s); else KMessageBox::error(this, i18n("Error importing %1: This file is not a valid KMM statement file.", url), i18n("Invalid Statement")); return result; } bool KMyMoneyApp::slotStatementImport(const MyMoneyStatement& s, bool silent) { bool result = false; // keep a copy of the statement if (KMyMoneySettings::logImportedStatements()) { QString logFile = QString("%1/kmm-statement-%2.txt").arg(KMyMoneySettings::logPath()).arg(d->m_statementXMLindex++); MyMoneyStatement::writeXMLFile(s, logFile); } // we use an object on the heap here, so that we can check the presence // of it during slotUpdateActions() by looking at the pointer. d->m_smtReader = new MyMoneyStatementReader; d->m_smtReader->setAutoCreatePayee(true); d->m_smtReader->setProgressCallback(&progressCallback); // disable all standard widgets during the import setEnabled(false); QStringList messages; result = d->m_smtReader->import(s, messages); bool transactionAdded = d->m_smtReader->anyTransactionAdded(); // get rid of the statement reader and tell everyone else // about the destruction by setting the pointer to zero delete d->m_smtReader; d->m_smtReader = 0; slotStatusProgressBar(-1, -1); ready(); // re-enable all standard widgets setEnabled(true); if (!d->m_collectingStatements && !silent) KMessageBox::informationList(this, i18n("The statement has been processed with the following results:"), messages, i18n("Statement stats")); else if (transactionAdded) d->m_statementResults += messages; slotUpdateActions();// Re-enable menu items after import via plugin. return result; } bool KMyMoneyApp::okToWriteFile(const QUrl &url) { Q_UNUSED(url) // check if the file exists and warn the user bool reallySaveFile = true; // TODO: port KF5 (NetAccess) //if (KIO::NetAccess::exists(url, KIO::NetAccess::SourceSide, this)) { // if (KMessageBox::warningYesNo(this, QLatin1String("") + i18n("The file %1 already exists. Do you really want to overwrite it?", url.toDisplayString(QUrl::PreferLocalFile)) + QLatin1String(""), i18n("File already exists")) != KMessageBox::Yes) // reallySaveFile = false; //} return reallySaveFile; } void KMyMoneyApp::slotSettings() { // if we already have an instance of the settings dialog, then use it if (KConfigDialog::showDialog("KMyMoney-Settings")) return; // otherwise, we have to create it KConfigDialog* dlg = new KSettingsKMyMoney(this, "KMyMoney-Settings", KMyMoneyGlobalSettings::self()); connect(dlg, &KConfigDialog::settingsChanged, this, &KMyMoneyApp::slotUpdateConfiguration); dlg->show(); } void KMyMoneyApp::slotShowCredits() { KAboutData aboutData = initializeCreditsData(); KAboutApplicationDialog dlg(aboutData, this); dlg.exec(); } void KMyMoneyApp::slotUpdateConfiguration() { MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneyGlobalSettings::firstFiscalMonth(), KMyMoneyGlobalSettings::firstFiscalDay()); LedgerSeperator::setFirstFiscalDate(KMyMoneyGlobalSettings::firstFiscalMonth(), KMyMoneyGlobalSettings::firstFiscalDay()); d->m_myMoneyView->updateViewType(); // update the sql storage module settings MyMoneyStorageSql::setStartDate(KMyMoneyGlobalSettings::startDate().date()); // update the report module settings MyMoneyReport::setLineWidth(KMyMoneyGlobalSettings::lineWidth()); // update the holiday region configuration setHolidayRegion(KMyMoneyGlobalSettings::holidayRegion()); d->m_myMoneyView->slotRefreshViews(); // re-read autosave configuration d->m_autoSaveEnabled = KMyMoneyGlobalSettings::autoSaveFile(); d->m_autoSavePeriod = KMyMoneyGlobalSettings::autoSavePeriod(); // stop timer if turned off but running if (d->m_autoSaveTimer->isActive() && !d->m_autoSaveEnabled) { d->m_autoSaveTimer->stop(); } // start timer if turned on and needed but not running if (!d->m_autoSaveTimer->isActive() && d->m_autoSaveEnabled && d->m_myMoneyView->dirty()) { d->m_autoSaveTimer->setSingleShot(true); d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000); } d->setThemedCSS(); // check if the recovery key is still valid or expires soon if (KMyMoneySettings::writeDataEncrypted() && KMyMoneySettings::encryptRecover()) { if (KGPGFile::GPGAvailable()) { KGPGFile file; QDateTime expirationDate = file.keyExpires(QLatin1String(recoveryKeyId)); if (expirationDate.isValid() && QDateTime::currentDateTime().daysTo(expirationDate) <= RECOVER_KEY_EXPIRATION_WARNING) { bool skipMessage = false; //get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); KConfigGroup grp; QDate lastWarned; if (kconfig) { grp = d->m_config->group("General Options"); lastWarned = grp.readEntry("LastRecoverKeyExpirationWarning", QDate()); if (QDate::currentDate() == lastWarned) { skipMessage = true; } } if (!skipMessage) { if (kconfig) { grp.writeEntry("LastRecoverKeyExpirationWarning", QDate::currentDate()); } KMessageBox::information(this, i18np("You have configured KMyMoney to use GPG to protect your data and to encrypt your data also with the KMyMoney recover key. This key is about to expire in %1 day. Please update the key from a keyserver using your GPG frontend (e.g. KGPG).", "You have configured KMyMoney to use GPG to protect your data and to encrypt your data also with the KMyMoney recover key. This key is about to expire in %1 days. Please update the key from a keyserver using your GPG frontend (e.g. KGPG).", QDateTime::currentDateTime().daysTo(expirationDate)), i18n("Recover key expires soon")); } } } } } void KMyMoneyApp::slotBackupFile() { // Save the file first so isLocalFile() works if (d->m_myMoneyView && d->m_myMoneyView->dirty()) { if (KMessageBox::questionYesNo(this, i18n("The file must be saved first " "before it can be backed up. Do you want to continue?")) == KMessageBox::No) { return; } slotFileSave(); } if (d->m_fileName.isEmpty()) return; if (!d->m_fileName.isLocalFile()) { KMessageBox::sorry(this, i18n("The current implementation of the backup functionality only supports local files as source files. Your current source file is '%1'.", d->m_fileName.url()), i18n("Local files only")); return; } QPointer backupDlg = new KBackupDlg(this); #ifdef Q_OS_WIN backupDlg->mountCheckBox->setEnabled(false); #endif int returncode = backupDlg->exec(); if (returncode == QDialog::Accepted && backupDlg != 0) { d->m_backupMount = backupDlg->mountCheckBox(); d->m_proc.clearProgram(); d->m_backupState = BACKUP_MOUNTING; d->m_mountpoint = backupDlg->mountPoint(); if (d->m_backupMount) { slotBackupMount(); } else { progressCallback(0, 300, ""); #ifdef Q_OS_WIN d->m_ignoreBackupExitCode = true; QTimer::singleShot(0, this, SLOT(slotBackupHandleEvents())); #else // If we don't have to mount a device, we just issue // a dummy command to start the copy operation d->m_proc.setProgram("true"); d->m_proc.start(); #endif } } delete backupDlg; } void KMyMoneyApp::slotBackupMount() { progressCallback(0, 300, i18n("Mounting %1", d->m_mountpoint)); d->m_proc.setProgram("mount"); d->m_proc << d->m_mountpoint; d->m_proc.start(); } bool KMyMoneyApp::slotBackupWriteFile() { QFileInfo fi(d->m_fileName.fileName()); QString today = QDate::currentDate().toString("-yyyy-MM-dd.") + fi.suffix(); QString backupfile = d->m_mountpoint + '/' + d->m_fileName.fileName(); KMyMoneyUtils::appendCorrectFileExt(backupfile, today); // check if file already exists and ask what to do QFile f(backupfile); if (f.exists()) { int answer = KMessageBox::warningContinueCancel(this, i18n("Backup file for today exists on that device. Replace?"), i18n("Backup"), KGuiItem(i18n("&Replace"))); if (answer == KMessageBox::Cancel) { return false; } } progressCallback(50, 0, i18n("Writing %1", backupfile)); d->m_proc.clearProgram(); #ifdef Q_OS_WIN d->m_proc << "cmd.exe" << "/c" << "copy" << "/b" << "/y"; d->m_proc << (QDir::toNativeSeparators(d->m_fileName.toLocalFile()) + "+ nul") << QDir::toNativeSeparators(backupfile); #else d->m_proc << "cp" << "-f"; d->m_proc << d->m_fileName.toLocalFile() << backupfile; #endif d->m_backupState = BACKUP_COPYING; d->m_proc.start(); return true; } void KMyMoneyApp::slotBackupUnmount() { progressCallback(250, 0, i18n("Unmounting %1", d->m_mountpoint)); d->m_proc.clearProgram(); d->m_proc.setProgram("umount"); d->m_proc << d->m_mountpoint; d->m_backupState = BACKUP_UNMOUNTING; d->m_proc.start(); } void KMyMoneyApp::slotBackupFinish() { d->m_backupState = BACKUP_IDLE; progressCallback(-1, -1, QString()); ready(); } void KMyMoneyApp::slotBackupHandleEvents() { switch (d->m_backupState) { case BACKUP_MOUNTING: if (d->m_ignoreBackupExitCode || (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0)) { d->m_ignoreBackupExitCode = false; d->m_backupResult = 0; if (!slotBackupWriteFile()) { d->m_backupResult = 1; if (d->m_backupMount) slotBackupUnmount(); else slotBackupFinish(); } } else { KMessageBox::information(this, i18n("Error mounting device"), i18n("Backup")); d->m_backupResult = 1; if (d->m_backupMount) slotBackupUnmount(); else slotBackupFinish(); } break; case BACKUP_COPYING: if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) { if (d->m_backupMount) { slotBackupUnmount(); } else { progressCallback(300, 0, i18nc("Backup done", "Done")); KMessageBox::information(this, i18n("File successfully backed up"), i18n("Backup")); slotBackupFinish(); } } else { qDebug("copy exit code is %d", d->m_proc.exitCode()); d->m_backupResult = 1; KMessageBox::information(this, i18n("Error copying file to device"), i18n("Backup")); if (d->m_backupMount) slotBackupUnmount(); else slotBackupFinish(); } break; case BACKUP_UNMOUNTING: if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) { progressCallback(300, 0, i18nc("Backup done", "Done")); if (d->m_backupResult == 0) KMessageBox::information(this, i18n("File successfully backed up"), i18n("Backup")); } else { KMessageBox::information(this, i18n("Error unmounting device"), i18n("Backup")); } slotBackupFinish(); break; default: qWarning("Unknown state for backup operation!"); progressCallback(-1, -1, QString()); ready(); break; } } void KMyMoneyApp::slotShowTipOfTheDay() { KTipDialog::showTip(d->m_myMoneyView, "", true); } void KMyMoneyApp::slotShowPreviousView() { } void KMyMoneyApp::slotShowNextView() { } void KMyMoneyApp::slotGenerateSql() { QPointer editor = new KGenerateSqlDlg(this); editor->setObjectName("Generate Database SQL"); editor->exec(); delete editor; } void KMyMoneyApp::slotToolsStartKCalc() { QString cmd = KMyMoneyGlobalSettings::externalCalculator(); // if none is present, we fall back to the default if (cmd.isEmpty()) { #if defined(Q_OS_WIN32) cmd = QLatin1String("calc"); #elif defined(Q_OS_MAC) cmd = QLatin1String("open -a Calculator"); #else cmd = QLatin1String("kcalc"); #endif } KRun::runCommand(cmd, this); } void KMyMoneyApp::slotFindTransaction() { if (d->m_searchDlg == 0) { d->m_searchDlg = new KFindTransactionDlg(this); connect(d->m_searchDlg, SIGNAL(destroyed()), this, SLOT(slotCloseSearchDialog())); connect(d->m_searchDlg, SIGNAL(transactionSelected(QString,QString)), d->m_myMoneyView, SLOT(slotLedgerSelected(QString,QString))); } d->m_searchDlg->show(); d->m_searchDlg->raise(); d->m_searchDlg->activateWindow(); } void KMyMoneyApp::slotCloseSearchDialog() { if (d->m_searchDlg) d->m_searchDlg->deleteLater(); d->m_searchDlg = 0; } void KMyMoneyApp::createInstitution(MyMoneyInstitution& institution) { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; try { file->addInstitution(institution); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Cannot add institution: %1", e.what())); } } void KMyMoneyApp::slotInstitutionNew() { MyMoneyInstitution institution; slotInstitutionNew(institution); } void KMyMoneyApp::slotInstitutionNew(MyMoneyInstitution& institution) { institution.clearId(); QPointer dlg = new KNewBankDlg(institution); if (dlg->exec() == QDialog::Accepted && dlg != 0) { institution = dlg->institution(); createInstitution(institution); } delete dlg; } void KMyMoneyApp::slotInstitutionEditEmpty() { slotInstitutionEdit(MyMoneyInstitution()); } void KMyMoneyApp::slotInstitutionEdit(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyInstitution)) return; // make sure the selected object has an id if (d->m_selectedInstitution.id().isEmpty()) return; try { MyMoneyFile* file = MyMoneyFile::instance(); //grab a pointer to the view, regardless of it being a account or institution view. MyMoneyInstitution institution = file->institution(d->m_selectedInstitution.id()); // bankSuccess is not checked anymore because d->m_file->institution will throw anyway QPointer dlg = new KNewBankDlg(institution); if (dlg->exec() == QDialog::Accepted && dlg != 0) { MyMoneyFileTransaction ft; try { file->modifyInstitution(dlg->institution()); ft.commit(); slotSelectInstitution(file->institution(dlg->institution().id())); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to store institution: %1", e.what())); } } delete dlg; } catch (const MyMoneyException &e) { if (!obj.id().isEmpty()) KMessageBox::information(this, i18n("Unable to edit institution: %1", e.what())); } } void KMyMoneyApp::slotInstitutionDelete() { MyMoneyFile *file = MyMoneyFile::instance(); try { MyMoneyInstitution institution = file->institution(d->m_selectedInstitution.id()); if ((KMessageBox::questionYesNo(this, i18n("

Do you really want to delete the institution %1?

", institution.name()))) == KMessageBox::No) return; MyMoneyFileTransaction ft; try { file->removeInstitution(institution); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to delete institution: %1", e.what())); } } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to delete institution: %1", e.what())); } } void KMyMoneyApp::createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal) { MyMoneyFile *file = MyMoneyFile::instance(); try { const MyMoneySecurity& sec = file->security(newAccount.currencyId()); // Check the opening balance if (openingBal.isPositive() && newAccount.accountGroup() == eMyMoney::Account::Type::Liability) { QString message = i18n("This account is a liability and if the " "opening balance represents money owed, then it should be negative. " "Negate the amount?\n\n" "Please click Yes to change the opening balance to %1,\n" "Please click No to leave the amount as %2,\n" "Please click Cancel to abort the account creation." , MyMoneyUtils::formatMoney(-openingBal, newAccount, sec) , MyMoneyUtils::formatMoney(openingBal, newAccount, sec)); int ans = KMessageBox::questionYesNoCancel(this, message); if (ans == KMessageBox::Yes) { openingBal = -openingBal; } else if (ans == KMessageBox::Cancel) return; } file->createAccount(newAccount, parentAccount, brokerageAccount, openingBal); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add account: %1", e.what())); } } void KMyMoneyApp::slotCategoryNew(const QString& name, QString& id) { MyMoneyAccount account; account.setName(name); slotCategoryNew(account, MyMoneyFile::instance()->expense()); id = account.id(); } void KMyMoneyApp::slotCategoryNew(MyMoneyAccount& account, const MyMoneyAccount& parent) { if (KMessageBox::questionYesNo(this, QString("%1").arg(i18n("

The category %1 currently does not exist. Do you want to create it?

The parent account will default to %2 but can be changed in the following dialog.

", account.name(), parent.name())), i18n("Create category"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "CreateNewCategories") == KMessageBox::Yes) { createCategory(account, parent); } else { // we should not keep the 'no' setting because that can confuse people like // I have seen in some usability tests. So we just delete it right away. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("CreateNewCategories")); } } } void KMyMoneyApp::slotCategoryNew(MyMoneyAccount& account) { slotCategoryNew(account, MyMoneyAccount()); } void KMyMoneyApp::slotCategoryNew() { MyMoneyAccount parent; MyMoneyAccount account; // Preselect the parent account by looking at the current selected account/category if (!d->m_selectedAccount.id().isEmpty() && d->m_selectedAccount.isIncomeExpense()) { MyMoneyFile* file = MyMoneyFile::instance(); try { parent = file->account(d->m_selectedAccount.id()); } catch (const MyMoneyException &) { } } createCategory(account, parent); } void KMyMoneyApp::createCategory(MyMoneyAccount& account, const MyMoneyAccount& parent) { if (!parent.id().isEmpty()) { try { // make sure parent account exists MyMoneyFile::instance()->account(parent.id()); account.setParentAccountId(parent.id()); account.setAccountType(parent.accountType()); } catch (const MyMoneyException &) { } } QPointer dialog = new KNewAccountDlg(account, false, true, 0, i18n("Create a new Category")); dialog->setOpeningBalanceShown(false); dialog->setOpeningDateShown(false); if (dialog->exec() == QDialog::Accepted && dialog != 0) { MyMoneyAccount parentAccount, brokerageAccount; account = dialog->account(); parentAccount = dialog->parentAccount(); MyMoneyFile::instance()->createAccount(account, parentAccount, brokerageAccount, MyMoneyMoney()); } delete dialog; } void KMyMoneyApp::slotAccountNew() { MyMoneyAccount acc; acc.setInstitutionId(d->m_selectedInstitution.id()); acc.setOpeningDate(KMyMoneyGlobalSettings::firstFiscalDate()); slotAccountNew(acc); } void KMyMoneyApp::slotAccountNew(MyMoneyAccount& account) { NewAccountWizard::Wizard* wizard = new NewAccountWizard::Wizard(); connect(wizard, SIGNAL(createInstitution(MyMoneyInstitution&)), this, SLOT(slotInstitutionNew(MyMoneyInstitution&))); connect(wizard, SIGNAL(createAccount(MyMoneyAccount&)), this, SLOT(slotAccountNew(MyMoneyAccount&))); connect(wizard, SIGNAL(createPayee(QString,QString&)), this, SLOT(slotPayeeNew(QString,QString&))); connect(wizard, SIGNAL(createCategory(MyMoneyAccount&,MyMoneyAccount)), this, SLOT(slotCategoryNew(MyMoneyAccount&,MyMoneyAccount))); wizard->setAccount(account); if (wizard->exec() == QDialog::Accepted) { MyMoneyAccount acc = wizard->account(); if (!(acc == MyMoneyAccount())) { MyMoneyFileTransaction ft; MyMoneyFile* file = MyMoneyFile::instance(); try { // create the account MyMoneyAccount parent = wizard->parentAccount(); file->addAccount(acc, parent); // tell the wizard about the account id which it // needs to create a possible schedule and transactions wizard->setAccount(acc); // store a possible conversion rate for the currency if (acc.currencyId() != file->baseCurrency().id()) { file->addPrice(wizard->conversionRate()); } // create the opening balance transaction if any file->createOpeningBalanceTransaction(acc, wizard->openingBalance()); // create the payout transaction for loans if any MyMoneyTransaction payoutTransaction = wizard->payoutTransaction(); if (payoutTransaction.splits().count() > 0) { file->addTransaction(payoutTransaction); } // create a brokerage account if selected MyMoneyAccount brokerageAccount = wizard->brokerageAccount(); if (!(brokerageAccount == MyMoneyAccount())) { file->addAccount(brokerageAccount, parent); } // create a possible schedule MyMoneySchedule sch = wizard->schedule(); if (!(sch == MyMoneySchedule())) { MyMoneyFile::instance()->addSchedule(sch); if (acc.isLoan()) { MyMoneyAccountLoan accLoan = MyMoneyFile::instance()->account(acc.id()); accLoan.setSchedule(sch.id()); acc = accLoan; MyMoneyFile::instance()->modifyAccount(acc); } } ft.commit(); account = acc; } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to create account: %1", e.what())); } } } delete wizard; } void KMyMoneyApp::slotInvestmentNew(MyMoneyAccount& account, const MyMoneyAccount& parent) { QString dontShowAgain = "CreateNewInvestments"; if (KMessageBox::questionYesNo(this, QString("") + i18n("The security %1 currently does not exist as sub-account of %2. " "Do you want to create it?", account.name(), parent.name()) + QString(""), i18n("Create security"), KStandardGuiItem::yes(), KStandardGuiItem::no(), dontShowAgain) == KMessageBox::Yes) { KNewInvestmentWizard dlg; dlg.setName(account.name()); if (dlg.exec() == QDialog::Accepted) { dlg.createObjects(parent.id()); account = dlg.account(); } } else { // in case the user said no but turned on the don't show again selection, we will enable // the message no matter what. Otherwise, the user is not able to use this feature // in the future anymore. KMessageBox::enableMessage(dontShowAgain); } } void KMyMoneyApp::slotInvestmentNew() { QPointer dlg = new KNewInvestmentWizard(this); if (dlg->exec() == QDialog::Accepted) dlg->createObjects(d->m_selectedAccount.id()); delete dlg; } void KMyMoneyApp::slotInvestmentEdit() { QPointer dlg = new KNewInvestmentWizard(d->m_selectedInvestment); if (dlg->exec() == QDialog::Accepted) dlg->createObjects(d->m_selectedAccount.id()); delete dlg; } void KMyMoneyApp::slotInvestmentDelete() { if (KMessageBox::questionYesNo(this, i18n("

Do you really want to delete the investment %1?

", d->m_selectedInvestment.name()), i18n("Delete investment"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "DeleteInvestment") == KMessageBox::Yes) { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; try { d->m_selectedAccount = MyMoneyAccount(); // CAUTION: deleting equity from investments view needs this, if ID of the equity to be deleted is the smallest from all file->removeAccount(d->m_selectedInvestment); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to delete investment: %1", e.what())); } } else { // we should not keep the 'no' setting because that can confuse people like // I have seen in some usability tests. So we just delete it right away. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("DeleteInvestment")); } } } void KMyMoneyApp::slotOnlinePriceUpdate() { if (!d->m_selectedInvestment.id().isEmpty()) { QPointer dlg = new KEquityPriceUpdateDlg(0, d->m_selectedInvestment.currencyId()); if (dlg->exec() == QDialog::Accepted && dlg != 0) { dlg->storePrices(); } delete dlg; } } void KMyMoneyApp::slotManualPriceUpdate() { if (!d->m_selectedInvestment.id().isEmpty()) { try { MyMoneySecurity security = MyMoneyFile::instance()->security(d->m_selectedInvestment.currencyId()); MyMoneySecurity currency = MyMoneyFile::instance()->security(security.tradingCurrency()); const MyMoneyPrice &price = MyMoneyFile::instance()->price(security.id(), currency.id()); QPointer calc = new KCurrencyCalculator(security, currency, MyMoneyMoney::ONE, price.rate(currency.id()), price.date(), MyMoneyMoney::precToDenom(security.pricePrecision())); calc->setupPriceEditor(); // The dialog takes care of adding the price if necessary calc->exec(); delete calc; } catch (const MyMoneyException &e) { qDebug("Error in price update: %s", qPrintable(e.what())); } } } void KMyMoneyApp::createSchedule(MyMoneySchedule newSchedule, MyMoneyAccount& newAccount) { MyMoneyFile* file = MyMoneyFile::instance(); // Add the schedule only if one exists // // Remember to modify the first split to reference the newly created account if (!newSchedule.name().isEmpty()) { try { // We assume at least 2 splits in the transaction MyMoneyTransaction t = newSchedule.transaction(); if (t.splitCount() < 2) { throw MYMONEYEXCEPTION("Transaction for schedule has less than 2 splits!"); } MyMoneyFileTransaction ft; try { file->addSchedule(newSchedule); // in case of a loan account, we keep a reference to this // schedule in the account if (newAccount.accountType() == eMyMoney::Account::Type::Loan || newAccount.accountType() == eMyMoney::Account::Type::AssetLoan) { newAccount.setValue("schedule", newSchedule.id()); file->modifyAccount(newAccount); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add scheduled transaction: %1", e.what())); } } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add scheduled transaction: %1", e.what())); } } } void KMyMoneyApp::slotAccountDelete() { if (d->m_selectedAccount.id().isEmpty()) return; // need an account ID MyMoneyFile* file = MyMoneyFile::instance(); // can't delete standard accounts or account which still have transactions assigned if (file->isStandardAccount(d->m_selectedAccount.id())) return; // check if the account is referenced by a transaction or schedule QBitArray skip((int)eStorage::Reference::Count); skip.fill(false); skip.setBit((int)eStorage::Reference::Account); skip.setBit((int)eStorage::Reference::Institution); skip.setBit((int)eStorage::Reference::Payee); skip.setBit((int)eStorage::Reference::Tag); skip.setBit((int)eStorage::Reference::Security); skip.setBit((int)eStorage::Reference::Currency); skip.setBit((int)eStorage::Reference::Price); bool hasReference = file->isReferenced(d->m_selectedAccount, skip); // make sure we only allow transactions in a 'category' (income/expense account) switch (d->m_selectedAccount.accountType()) { case eMyMoney::Account::Type::Income: case eMyMoney::Account::Type::Expense: break; default: // if the account is still referenced if (hasReference) { return; } break; } // if we get here and still have transactions referencing the account, we // need to check with the user to possibly re-assign them to a different account bool needAskUser = true; bool exit = false; MyMoneyFileTransaction ft; if (hasReference) { // show transaction reassignment dialog needAskUser = false; KCategoryReassignDlg* dlg = new KCategoryReassignDlg(this); QString categoryId = dlg->show(d->m_selectedAccount); delete dlg; // and kill the dialog if (categoryId.isEmpty()) return; // the user aborted the dialog, so let's abort as well MyMoneyAccount newCategory = file->account(categoryId); try { { KMSTATUS(i18n("Adjusting transactions...")); /* d->m_selectedAccount.id() is the old id, categoryId the new one Now search all transactions and schedules that reference d->m_selectedAccount.id() and replace that with categoryId. */ // get the list of all transactions that reference the old account MyMoneyTransactionFilter filter(d->m_selectedAccount.id()); filter.setReportAllSplits(false); QList tlist; QList::iterator it_t; file->transactionList(tlist, filter); slotStatusProgressBar(0, tlist.count()); int cnt = 0; for (it_t = tlist.begin(); it_t != tlist.end(); ++it_t) { slotStatusProgressBar(++cnt, 0); MyMoneyTransaction t = (*it_t); if (t.replaceId(categoryId, d->m_selectedAccount.id())) file->modifyTransaction(t); } slotStatusProgressBar(tlist.count(), 0); } // now fix all schedules { KMSTATUS(i18n("Adjusting scheduled transactions...")); QList slist = file->scheduleList(d->m_selectedAccount.id()); QList::iterator it_s; int cnt = 0; slotStatusProgressBar(0, slist.count()); for (it_s = slist.begin(); it_s != slist.end(); ++it_s) { slotStatusProgressBar(++cnt, 0); MyMoneySchedule sch = (*it_s); if (sch.replaceId(categoryId, d->m_selectedAccount.id())) { file->modifySchedule(sch); } } slotStatusProgressBar(slist.count(), 0); } // now fix all budgets { KMSTATUS(i18n("Adjusting budgets...")); QList blist = file->budgetList(); QList::const_iterator it_b; for (it_b = blist.constBegin(); it_b != blist.constEnd(); ++it_b) { if ((*it_b).hasReferenceTo(d->m_selectedAccount.id())) { MyMoneyBudget b = (*it_b); MyMoneyBudget::AccountGroup fromBudget = b.account(d->m_selectedAccount.id()); MyMoneyBudget::AccountGroup toBudget = b.account(categoryId); toBudget += fromBudget; b.setAccount(toBudget, categoryId); b.removeReference(d->m_selectedAccount.id()); file->modifyBudget(b); } } slotStatusProgressBar(blist.count(), 0); } } catch (MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to exchange category %1 with category %2. Reason: %3", d->m_selectedAccount.name(), newCategory.name(), e.what())); exit = true; } slotStatusProgressBar(-1, -1); } if (exit) return; // retain the account name for a possible later usage in the error message box // since the account removal notifies the views the selected account can be changed // so we make sure by doing this that we display the correct name in the error message QString selectedAccountName = d->m_selectedAccount.name(); // at this point, we must not have a reference to the account // to be deleted anymore switch (d->m_selectedAccount.accountGroup()) { // special handling for categories to allow deleting of empty subcategories case eMyMoney::Account::Type::Income: case eMyMoney::Account::Type::Expense: { // open a compound statement here to be able to declare variables // which would otherwise not work within a case label. // case A - only a single, unused category without subcats selected if (d->m_selectedAccount.accountList().isEmpty()) { if (!needAskUser || (KMessageBox::questionYesNo(this, QString("") + i18n("Do you really want to delete category %1?", selectedAccountName) + QString("")) == KMessageBox::Yes)) { try { file->removeAccount(d->m_selectedAccount); d->m_selectedAccount.clearId(); slotUpdateActions(); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::error(this, QString("") + i18n("Unable to delete category %1. Cause: %2", selectedAccountName, e.what()) + QString("")); } } return; } // case B - we have some subcategories, maybe the user does not want to // delete them all, but just the category itself? MyMoneyAccount parentAccount = file->account(d->m_selectedAccount.parentAccountId()); QStringList accountsToReparent; int result = KMessageBox::questionYesNoCancel(this, QString("") + i18n("Do you want to delete category %1 with all its sub-categories or only " "the category itself? If you only delete the category itself, all its sub-categories " "will be made sub-categories of %2.", selectedAccountName, parentAccount.name()) + QString(""), QString(), KGuiItem(i18n("Delete all")), KGuiItem(i18n("Just the category"))); if (result == KMessageBox::Cancel) return; // cancel pressed? ok, no delete then... // "No" means "Just the category" and that means we need to reparent all subaccounts bool need_confirmation = false; // case C - User only wants to delete the category itself if (result == KMessageBox::No) accountsToReparent = d->m_selectedAccount.accountList(); else { // case D - User wants to delete all subcategories, now check all subcats of // d->m_selectedAccount and remember all that cannot be deleted and // must be "reparented" foreach (const auto accountID, d->m_selectedAccount.accountList()) { // reparent account if a transaction is assigned if (file->transactionCount(accountID) != 0) accountsToReparent.push_back(accountID); else if (!file->account(accountID).accountList().isEmpty()) { // or if we have at least one sub-account that is used for transactions if (!file->hasOnlyUnusedAccounts(file->account(accountID).accountList())) { accountsToReparent.push_back(accountID); //qDebug() << "subaccount not empty"; } } } if (!accountsToReparent.isEmpty()) need_confirmation = true; } if (!accountsToReparent.isEmpty() && need_confirmation) { if (KMessageBox::questionYesNo(this, i18n("

Some sub-categories of category %1 cannot " "be deleted, because they are still used. They will be made sub-categories of %2. Proceed?

", selectedAccountName, parentAccount.name())) != KMessageBox::Yes) { return; // user gets wet feet... } } // all good, now first reparent selected sub-categories try { MyMoneyAccount parent = file->account(d->m_selectedAccount.parentAccountId()); for (QStringList::const_iterator it = accountsToReparent.constBegin(); it != accountsToReparent.constEnd(); ++it) { MyMoneyAccount child = file->account(*it); file->reparentAccount(child, parent); } // reload the account because the sub-account list might have changed d->m_selectedAccount = file->account(d->m_selectedAccount.id()); // now recursively delete remaining sub-categories file->removeAccountList(d->m_selectedAccount.accountList()); // don't forget to update d->m_selectedAccount, because we still have a copy of // the old account list, which is no longer valid d->m_selectedAccount = file->account(d->m_selectedAccount.id()); } catch (const MyMoneyException &e) { KMessageBox::error(this, QString("") + i18n("Unable to delete a sub-category of category %1. Reason: %2", selectedAccountName, e.what()) + QString("")); return; } } break; // the category/account is deleted after the switch default: if (!d->m_selectedAccount.accountList().isEmpty()) return; // can't delete accounts which still have subaccounts if (KMessageBox::questionYesNo(this, i18n("

Do you really want to " "delete account %1?

", selectedAccountName)) != KMessageBox::Yes) { return; // ok, you don't want to? why did you click then, hmm? } } // switch; try { file->removeAccount(d->m_selectedAccount); d->m_selectedAccount.clearId(); slotUpdateActions(); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to delete account '%1'. Cause: %2", selectedAccountName, e.what())); } } void KMyMoneyApp::slotAccountEdit() { MyMoneyFile* file = MyMoneyFile::instance(); if (!d->m_selectedAccount.id().isEmpty()) { if (!file->isStandardAccount(d->m_selectedAccount.id())) { if (d->m_selectedAccount.accountType() != eMyMoney::Account::Type::Loan && d->m_selectedAccount.accountType() != eMyMoney::Account::Type::AssetLoan) { QString caption; bool category = false; switch (d->m_selectedAccount.accountGroup()) { default: caption = i18n("Edit account '%1'", d->m_selectedAccount.name()); break; case eMyMoney::Account::Type::Expense: case eMyMoney::Account::Type::Income: caption = i18n("Edit category '%1'", d->m_selectedAccount.name()); category = true; break; } // set a status message so that the application can't be closed until the editing is done slotStatusMsg(caption); QString tid = file->openingBalanceTransaction(d->m_selectedAccount); MyMoneyTransaction t; MyMoneySplit s0, s1; QPointer dlg = new KNewAccountDlg(d->m_selectedAccount, true, category, 0, caption); if (category) { dlg->setOpeningBalanceShown(false); dlg->setOpeningDateShown(false); tid.clear(); } else { if (!tid.isEmpty()) { try { t = file->transaction(tid); s0 = t.splitByAccount(d->m_selectedAccount.id()); s1 = t.splitByAccount(d->m_selectedAccount.id(), false); dlg->setOpeningBalance(s0.shares()); if (d->m_selectedAccount.accountGroup() == eMyMoney::Account::Type::Liability) { dlg->setOpeningBalance(-s0.shares()); } } catch (const MyMoneyException &e) { qDebug() << "Error retrieving opening balance transaction " << tid << ": " << e.what() << "\n"; tid.clear(); } } } // check for online modules QMap::const_iterator it_plugin = d->m_onlinePlugins.constEnd(); const MyMoneyKeyValueContainer& kvp = d->m_selectedAccount.onlineBankingSettings(); if (!kvp["provider"].isEmpty()) { // if we have an online provider for this account, we need to check // that we have the corresponding plugin. If that exists, we ask it // to provide an additional tab for the account editor. it_plugin = d->m_onlinePlugins.constFind(kvp["provider"]); if (it_plugin != d->m_onlinePlugins.constEnd()) { QString name; QWidget *w = (*it_plugin)->accountConfigTab(d->m_selectedAccount, name); dlg->addTab(w, name); } } if (dlg != 0 && dlg->exec() == QDialog::Accepted) { try { MyMoneyFileTransaction ft; MyMoneyAccount account = dlg->account(); MyMoneyAccount parent = dlg->parentAccount(); if (it_plugin != d->m_onlinePlugins.constEnd()) { account.setOnlineBankingSettings((*it_plugin)->onlineBankingSettings(account.onlineBankingSettings())); } MyMoneyMoney bal = dlg->openingBalance(); if (d->m_selectedAccount.accountGroup() == eMyMoney::Account::Type::Liability) { bal = -bal; } // we need to modify first, as reparent would override all other changes file->modifyAccount(account); if (account.parentAccountId() != parent.id()) { file->reparentAccount(account, parent); } if (!tid.isEmpty() && dlg->openingBalance().isZero()) { file->removeTransaction(t); } else if (!tid.isEmpty() && !dlg->openingBalance().isZero()) { s0.setShares(bal); s0.setValue(bal); t.modifySplit(s0); s1.setShares(-bal); s1.setValue(-bal); t.modifySplit(s1); t.setPostDate(account.openingDate()); file->modifyTransaction(t); } else if (tid.isEmpty() && !dlg->openingBalance().isZero()) { file->createOpeningBalanceTransaction(d->m_selectedAccount, bal); } ft.commit(); // reload the account object as it might have changed in the meantime slotSelectAccount(file->account(account.id())); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to modify account '%1'. Cause: %2", d->m_selectedAccount.name(), e.what())); } } delete dlg; ready(); } else { QPointer wizard = new KEditLoanWizard(d->m_selectedAccount); connect(wizard, SIGNAL(newCategory(MyMoneyAccount&)), this, SLOT(slotCategoryNew(MyMoneyAccount&))); connect(wizard, SIGNAL(createPayee(QString,QString&)), this, SLOT(slotPayeeNew(QString,QString&))); if (wizard->exec() == QDialog::Accepted && wizard != 0) { MyMoneySchedule sch; try { MyMoneySchedule sch = file->schedule(d->m_selectedAccount.value("schedule").toLatin1()); } catch (const MyMoneyException &e) { qDebug() << "schedule" << d->m_selectedAccount.value("schedule").toLatin1() << "not found"; } if (!(d->m_selectedAccount == wizard->account()) || !(sch == wizard->schedule())) { MyMoneyFileTransaction ft; try { file->modifyAccount(wizard->account()); if (!sch.id().isEmpty()) { sch = wizard->schedule(); } try { file->schedule(sch.id()); file->modifySchedule(sch); ft.commit(); } catch (const MyMoneyException &) { try { if(sch.transaction().splitCount() >= 2) { file->addSchedule(sch); } ft.commit(); } catch (const MyMoneyException &e) { qDebug("Cannot add schedule: '%s'", qPrintable(e.what())); } } } catch (const MyMoneyException &e) { qDebug("Unable to modify account %s: '%s'", qPrintable(d->m_selectedAccount.name()), qPrintable(e.what())); } } } delete wizard; } } } } QList > KMyMoneyApp::Private::automaticReconciliation(const MyMoneyAccount &account, const QList > &transactions, const MyMoneyMoney &amount) { static const int NR_OF_STEPS_LIMIT = 300000; static const int PROGRESSBAR_STEPS = 1000; QList > result = transactions; KMSTATUS(i18n("Running automatic reconciliation")); int progressBarIndex = 0; kmymoney->slotStatusProgressBar(progressBarIndex, NR_OF_STEPS_LIMIT / PROGRESSBAR_STEPS); // optimize the most common case - all transactions should be cleared QListIterator > itTransactionSplitResult(result); MyMoneyMoney transactionsBalance; while (itTransactionSplitResult.hasNext()) { const QPair &transactionSplit = itTransactionSplitResult.next(); transactionsBalance += transactionSplit.second.shares(); } if (amount == transactionsBalance) { result = transactions; return result; } kmymoney->slotStatusProgressBar(progressBarIndex++, 0); // only one transaction is uncleared itTransactionSplitResult.toFront(); int index = 0; while (itTransactionSplitResult.hasNext()) { const QPair &transactionSplit = itTransactionSplitResult.next(); if (transactionsBalance - transactionSplit.second.shares() == amount) { result.removeAt(index); return result; } index++; } kmymoney->slotStatusProgressBar(progressBarIndex++, 0); // more than one transaction is uncleared - apply the algorithm result.clear(); const MyMoneySecurity &security = MyMoneyFile::instance()->security(account.currencyId()); double precision = 0.1 / account.fraction(security); QList sumList; sumList << MyMoneyMoney(); QMap > > sumToComponentsMap; // compute the possible matches QListIterator > itTransactionSplit(transactions); while (itTransactionSplit.hasNext()) { const QPair &transactionSplit = itTransactionSplit.next(); QListIterator itSum(sumList); QList tempList; while (itSum.hasNext()) { const MyMoneyMoney &sum = itSum.next(); QList > splitIds; splitIds << qMakePair(transactionSplit.first.id(), transactionSplit.second.id()); if (sumToComponentsMap.contains(sum)) { if (sumToComponentsMap.value(sum).contains(qMakePair(transactionSplit.first.id(), transactionSplit.second.id()))) { continue; } splitIds.append(sumToComponentsMap.value(sum)); } tempList << transactionSplit.second.shares() + sum; sumToComponentsMap[transactionSplit.second.shares() + sum] = splitIds; int size = sumToComponentsMap.size(); if (size % PROGRESSBAR_STEPS == 0) { kmymoney->slotStatusProgressBar(progressBarIndex++, 0); } if (size > NR_OF_STEPS_LIMIT) { return result; // it's taking too much resources abort the algorithm } } QList unionList; unionList.append(tempList); unionList.append(sumList); qSort(unionList); sumList.clear(); MyMoneyMoney smallestSumFromUnion = unionList.first(); sumList.append(smallestSumFromUnion); QListIterator itUnion(unionList); while (itUnion.hasNext()) { MyMoneyMoney sumFromUnion = itUnion.next(); if (smallestSumFromUnion < MyMoneyMoney(1 - precision / transactions.size())*sumFromUnion) { smallestSumFromUnion = sumFromUnion; sumList.append(sumFromUnion); } } } kmymoney->slotStatusProgressBar(NR_OF_STEPS_LIMIT / PROGRESSBAR_STEPS, 0); if (sumToComponentsMap.contains(amount)) { QListIterator > itTransactionSplit(transactions); while (itTransactionSplit.hasNext()) { const QPair &transactionSplit = itTransactionSplit.next(); const QList > &splitIds = sumToComponentsMap.value(amount); if (splitIds.contains(qMakePair(transactionSplit.first.id(), transactionSplit.second.id()))) { result.append(transactionSplit); } } } #ifdef KMM_DEBUG qDebug("For the amount %s a number of %d possible sums where computed from the set of %d transactions: ", qPrintable(MyMoneyUtils::formatMoney(amount, security)), sumToComponentsMap.size(), transactions.size()); #endif kmymoney->slotStatusProgressBar(-1, -1); return result; } void KMyMoneyApp::slotAccountReconcileStart() { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyAccount account; // we cannot reconcile standard accounts if (!file->isStandardAccount(d->m_selectedAccount.id())) { // check if we can reconcile this account // it make's sense for asset and liability accounts try { // check if we have overdue schedules for this account QList schedules = file->scheduleList(d->m_selectedAccount.id(), eMyMoney::Schedule::Type::Any, eMyMoney::Schedule::Occurrence::Any, eMyMoney::Schedule::PaymentType::Any, QDate(), QDate(), true); if (schedules.count() > 0) { if (KMessageBox::questionYesNo(this, i18n("KMyMoney has detected some overdue scheduled transactions for this account. Do you want to enter those scheduled transactions now?"), i18n("Scheduled transactions found")) == KMessageBox::Yes) { QMap skipMap; bool processedOne; eDialogs::ScheduleResultCode rc = eDialogs::ScheduleResultCode::Enter; do { processedOne = false; QList::const_iterator it_sch; for (it_sch = schedules.constBegin(); (rc != eDialogs::ScheduleResultCode::Cancel) && (it_sch != schedules.constEnd()); ++it_sch) { MyMoneySchedule sch(*(it_sch)); // and enter it if it is not on the skip list if (skipMap.find((*it_sch).id()) == skipMap.end()) { rc = enterSchedule(sch, false, true); if (rc == eDialogs::ScheduleResultCode::Ignore) { skipMap[(*it_sch).id()] = true; } } } // reload list (maybe this schedule needs to be added again) schedules = file->scheduleList(d->m_selectedAccount.id(), eMyMoney::Schedule::Type::Any, eMyMoney::Schedule::Occurrence::Any, eMyMoney::Schedule::PaymentType::Any, QDate(), QDate(), true); } while (processedOne); } } account = file->account(d->m_selectedAccount.id()); // get rid of previous run. delete d->m_endingBalanceDlg; d->m_endingBalanceDlg = new KEndingBalanceDlg(account, this); if (account.isAssetLiability()) { connect(d->m_endingBalanceDlg, SIGNAL(createPayee(QString,QString&)), this, SLOT(slotPayeeNew(QString,QString&))); connect(d->m_endingBalanceDlg, SIGNAL(createCategory(MyMoneyAccount&,MyMoneyAccount)), this, SLOT(slotCategoryNew(MyMoneyAccount&,MyMoneyAccount))); if (d->m_endingBalanceDlg->exec() == QDialog::Accepted) { if (KMyMoneyGlobalSettings::autoReconciliation()) { MyMoneyMoney startBalance = d->m_endingBalanceDlg->previousBalance(); MyMoneyMoney endBalance = d->m_endingBalanceDlg->endingBalance(); QDate endDate = d->m_endingBalanceDlg->statementDate(); QList > transactionList; MyMoneyTransactionFilter filter(account.id()); filter.addState((int)eMyMoney::TransactionFilter::State::Cleared); filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled); filter.setDateFilter(QDate(), endDate); filter.setConsiderCategory(false); filter.setReportAllSplits(true); file->transactionList(transactionList, filter); QList > result = d->automaticReconciliation(account, transactionList, endBalance - startBalance); if (!result.empty()) { QString message = i18n("KMyMoney has detected transactions matching your reconciliation data.\nWould you like KMyMoney to clear these transactions for you?"); if (KMessageBox::questionYesNo(this, message, i18n("Automatic reconciliation"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "AcceptAutomaticReconciliation") == KMessageBox::Yes) { // mark the transactions cleared KMyMoneyRegister::SelectedTransactions oldSelection = d->m_selectedTransactions; d->m_selectedTransactions.clear(); QListIterator > itTransactionSplitResult(result); while (itTransactionSplitResult.hasNext()) { const QPair &transactionSplit = itTransactionSplitResult.next(); d->m_selectedTransactions.append(KMyMoneyRegister::SelectedTransaction(transactionSplit.first, transactionSplit.second, QString())); } // mark all transactions in d->m_selectedTransactions as 'Cleared' markTransaction(eMyMoney::Split::State::Cleared); d->m_selectedTransactions = oldSelection; } } } if (d->m_myMoneyView->startReconciliation(account, d->m_endingBalanceDlg->statementDate(), d->m_endingBalanceDlg->endingBalance())) { // check if the user requests us to create interest // or charge transactions. MyMoneyTransaction ti = d->m_endingBalanceDlg->interestTransaction(); MyMoneyTransaction tc = d->m_endingBalanceDlg->chargeTransaction(); MyMoneyFileTransaction ft; try { if (ti != MyMoneyTransaction()) { MyMoneyFile::instance()->addTransaction(ti); } if (tc != MyMoneyTransaction()) { MyMoneyFile::instance()->addTransaction(tc); } ft.commit(); } catch (const MyMoneyException &e) { qWarning("interest transaction not stored: '%s'", qPrintable(e.what())); } // reload the account object as it might have changed in the meantime d->m_reconciliationAccount = file->account(account.id()); slotUpdateActions(); } } } } catch (const MyMoneyException &) { } } } void KMyMoneyApp::slotAccountReconcileFinish() { MyMoneyFile* file = MyMoneyFile::instance(); if (!d->m_reconciliationAccount.id().isEmpty()) { // retrieve list of all transactions that are not reconciled or cleared QList > transactionList; MyMoneyTransactionFilter filter(d->m_reconciliationAccount.id()); filter.addState((int)eMyMoney::TransactionFilter::State::Cleared); filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled); filter.setDateFilter(QDate(), d->m_endingBalanceDlg->statementDate()); filter.setConsiderCategory(false); filter.setReportAllSplits(true); file->transactionList(transactionList, filter); MyMoneyMoney balance = MyMoneyFile::instance()->balance(d->m_reconciliationAccount.id(), d->m_endingBalanceDlg->statementDate()); MyMoneyMoney actBalance, clearedBalance; actBalance = clearedBalance = balance; // walk the list of transactions to figure out the balance(s) QList >::const_iterator it; for (it = transactionList.constBegin(); it != transactionList.constEnd(); ++it) { if ((*it).second.reconcileFlag() == eMyMoney::Split::State::NotReconciled) { clearedBalance -= (*it).second.shares(); } } if (d->m_endingBalanceDlg->endingBalance() != clearedBalance) { QString message = i18n("You are about to finish the reconciliation of this account with a difference between your bank statement and the transactions marked as cleared.\n" "Are you sure you want to finish the reconciliation?"); if (KMessageBox::questionYesNo(this, message, i18n("Confirm end of reconciliation"), KStandardGuiItem::yes(), KStandardGuiItem::no()) == KMessageBox::No) return; } MyMoneyFileTransaction ft; // refresh object d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id()); // Turn off reconciliation mode d->m_myMoneyView->finishReconciliation(d->m_reconciliationAccount); // only update the last statement balance here, if we haven't a newer one due // to download of online statements. if (d->m_reconciliationAccount.value("lastImportedTransactionDate").isEmpty() || QDate::fromString(d->m_reconciliationAccount.value("lastImportedTransactionDate"), Qt::ISODate) < d->m_endingBalanceDlg->statementDate()) { d->m_reconciliationAccount.setValue("lastStatementBalance", d->m_endingBalanceDlg->endingBalance().toString()); // in case we override the last statement balance here, we have to make sure // that we don't show the online balance anymore, as it might be different d->m_reconciliationAccount.deletePair("lastImportedTransactionDate"); } d->m_reconciliationAccount.setLastReconciliationDate(d->m_endingBalanceDlg->statementDate()); // keep a record of this reconciliation d->m_reconciliationAccount.addReconciliation(d->m_endingBalanceDlg->statementDate(), d->m_endingBalanceDlg->endingBalance()); d->m_reconciliationAccount.deletePair("lastReconciledBalance"); d->m_reconciliationAccount.deletePair("statementBalance"); d->m_reconciliationAccount.deletePair("statementDate"); try { // update the account data file->modifyAccount(d->m_reconciliationAccount); /* // collect the list of cleared splits for this account filter.clear(); filter.addAccount(d->m_reconciliationAccount.id()); filter.addState(eMyMoney::TransactionFilter::Cleared); filter.setConsiderCategory(false); filter.setReportAllSplits(true); file->transactionList(transactionList, filter); */ // walk the list of transactions/splits and mark the cleared ones as reconciled QList >::iterator it; for (it = transactionList.begin(); it != transactionList.end(); ++it) { MyMoneySplit sp = (*it).second; // skip the ones that are not marked cleared if (sp.reconcileFlag() != eMyMoney::Split::State::Cleared) continue; // always retrieve a fresh copy of the transaction because we // might have changed it already with another split MyMoneyTransaction t = file->transaction((*it).first.id()); sp.setReconcileFlag(eMyMoney::Split::State::Reconciled); sp.setReconcileDate(d->m_endingBalanceDlg->statementDate()); t.modifySplit(sp); // update the engine ... file->modifyTransaction(t); // ... and the list (*it) = qMakePair(t, sp); } ft.commit(); // reload account data from engine as the data might have changed in the meantime d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id()); emit accountReconciled(d->m_reconciliationAccount, d->m_endingBalanceDlg->statementDate(), d->m_endingBalanceDlg->previousBalance(), d->m_endingBalanceDlg->endingBalance(), transactionList); } catch (const MyMoneyException &) { qDebug("Unexpected exception when setting cleared to reconcile"); } } // Turn off reconciliation mode d->m_reconciliationAccount = MyMoneyAccount(); slotUpdateActions(); } void KMyMoneyApp::slotAccountReconcilePostpone() { MyMoneyFileTransaction ft; MyMoneyFile* file = MyMoneyFile::instance(); if (!d->m_reconciliationAccount.id().isEmpty()) { // refresh object d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id()); // Turn off reconciliation mode d->m_myMoneyView->finishReconciliation(d->m_reconciliationAccount); d->m_reconciliationAccount.setValue("lastReconciledBalance", d->m_endingBalanceDlg->previousBalance().toString()); d->m_reconciliationAccount.setValue("statementBalance", d->m_endingBalanceDlg->endingBalance().toString()); d->m_reconciliationAccount.setValue("statementDate", d->m_endingBalanceDlg->statementDate().toString(Qt::ISODate)); try { file->modifyAccount(d->m_reconciliationAccount); ft.commit(); d->m_reconciliationAccount = MyMoneyAccount(); slotUpdateActions(); } catch (const MyMoneyException &) { qDebug("Unexpected exception when setting last reconcile info into account"); ft.rollback(); d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id()); } } } void KMyMoneyApp::slotAccountOpenEmpty() { slotAccountOpen(MyMoneyAccount()); } void KMyMoneyApp::slotAccountOpen(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyAccount)) return; MyMoneyFile* file = MyMoneyFile::instance(); QString id = d->m_selectedAccount.id(); // if the caller passed a non-empty object, we need to select that if (!obj.id().isEmpty()) { id = obj.id(); } // we cannot reconcile standard accounts if (!file->isStandardAccount(id)) { // check if we can open this account // currently it make's sense for asset and liability accounts try { MyMoneyAccount account = file->account(id); d->m_myMoneyView->slotLedgerSelected(account.id()); } catch (const MyMoneyException &) { } } } void KMyMoneyApp::enableCloseAccountAction(const MyMoneyAccount& acc) { QAction *a = actionCollection()->action(s_Actions[Action::AccountClose]); switch (canCloseAccount(acc)) { case KMyMoneyUtils::AccountCanClose: { a->setEnabled(true); break; } case KMyMoneyUtils::AccountBalanceNonZero: { a->setEnabled(false); a->setToolTip(i18n("The balance of the account must be zero before the account can be closed")); break; } case KMyMoneyUtils::AccountChildrenOpen: { a->setEnabled(false); a->setToolTip(i18n("All subaccounts must be closed before the account can be closed")); break; } case KMyMoneyUtils::AccountScheduleReference: { a->setEnabled(false); a->setToolTip(i18n("This account is still included in an active schedule")); break; } } } KMyMoneyUtils::CanCloseAccountCodeE KMyMoneyApp::canCloseAccount(const MyMoneyAccount& acc) const { // balance must be zero if (!acc.balance().isZero()) return KMyMoneyUtils::AccountBalanceNonZero; // all children must be already closed QStringList::const_iterator it_a; for (it_a = acc.accountList().constBegin(); it_a != acc.accountList().constEnd(); ++it_a) { MyMoneyAccount a = MyMoneyFile::instance()->account(*it_a); if (!a.isClosed()) { return KMyMoneyUtils::AccountChildrenOpen; } } // there must be no unfinished schedule referencing the account QList list = MyMoneyFile::instance()->scheduleList(); QList::const_iterator it_l; for (it_l = list.constBegin(); it_l != list.constEnd(); ++it_l) { if ((*it_l).isFinished()) continue; if ((*it_l).hasReferenceTo(acc.id())) return KMyMoneyUtils::AccountScheduleReference; } return KMyMoneyUtils::AccountCanClose; } void KMyMoneyApp::slotAccountClose() { MyMoneyAccount a; if (!d->m_selectedInvestment.id().isEmpty()) a = d->m_selectedInvestment; else if (!d->m_selectedAccount.id().isEmpty()) a = d->m_selectedAccount; if (a.id().isEmpty()) return; // need an account ID MyMoneyFileTransaction ft; try { a.setClosed(true); MyMoneyFile::instance()->modifyAccount(a); ft.commit(); if (KMyMoneyGlobalSettings::hideClosedAccounts()) { KMessageBox::information(this, QString("") + i18n("You have closed this account. It remains in the system because you have transactions which still refer to it, but it is not shown in the views. You can make it visible again by going to the View menu and selecting Show all accounts or by deselecting the Do not show closed accounts setting.") + QString(""), i18n("Information"), "CloseAccountInfo"); } } catch (const MyMoneyException &) { } } void KMyMoneyApp::slotAccountReopen() { MyMoneyAccount a; if (!d->m_selectedInvestment.id().isEmpty()) a = d->m_selectedInvestment; else if (!d->m_selectedAccount.id().isEmpty()) a = d->m_selectedAccount; if (a.id().isEmpty()) return; // need an account ID MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; try { while (a.isClosed()) { a.setClosed(false); file->modifyAccount(a); a = file->account(a.parentAccountId()); } ft.commit(); } catch (const MyMoneyException &) { } } void KMyMoneyApp::slotReparentAccount(const MyMoneyAccount& _src, const MyMoneyInstitution& _dst) { MyMoneyAccount src(_src); src.setInstitutionId(_dst.id()); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyAccount(src); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("

%1 cannot be moved to institution %2. Reason: %3

", src.name(), _dst.name(), e.what())); } } void KMyMoneyApp::slotReparentAccount(const MyMoneyAccount& _src, const MyMoneyAccount& _dst) { MyMoneyAccount src(_src); MyMoneyAccount dst(_dst); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->reparentAccount(src, dst); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("

%1 cannot be moved to %2. Reason: %3

", src.name(), dst.name(), e.what())); } } void KMyMoneyApp::slotAccountTransactionReport() { // Generate a transaction report that contains transactions for only the // currently selected account. if (!d->m_selectedAccount.id().isEmpty()) { MyMoneyReport report( MyMoneyReport::eAccount, MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory, eMyMoney::TransactionFilter::Date::YearToDate, MyMoneyReport::eDetailAll, i18n("%1 YTD Account Transactions", d->m_selectedAccount.name()), i18n("Generated Report") ); report.setGroup(i18n("Transactions")); report.addAccount(d->m_selectedAccount.id()); d->m_myMoneyView->slotShowReport(report); } } void KMyMoneyApp::slotScheduleNew() { slotScheduleNew(MyMoneyTransaction()); } void KMyMoneyApp::slotScheduleNew(const MyMoneyTransaction& _t, eMyMoney::Schedule::Occurrence occurrence) { MyMoneySchedule schedule; schedule.setOccurrence(occurrence); // if the schedule is based on an existing transaction, // we take the post date and project it to the next // schedule in a month. if (_t != MyMoneyTransaction()) { MyMoneyTransaction t(_t); schedule.setTransaction(t); if (occurrence != eMyMoney::Schedule::Occurrence::Once) schedule.setNextDueDate(schedule.nextPayment(t.postDate())); } QPointer dlg = new KEditScheduleDlg(schedule, this); TransactionEditor* transactionEditor = dlg->startEdit(); if (transactionEditor) { KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart()); if (dlg->exec() == QDialog::Accepted && dlg != 0) { MyMoneyFileTransaction ft; try { schedule = dlg->schedule(); MyMoneyFile::instance()->addSchedule(schedule); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to add scheduled transaction: %1", e.what()), i18n("Add scheduled transaction")); } } } delete transactionEditor; delete dlg; } void KMyMoneyApp::slotScheduleEdit() { if (!d->m_selectedSchedule.id().isEmpty()) { try { MyMoneySchedule schedule = MyMoneyFile::instance()->schedule(d->m_selectedSchedule.id()); KEditScheduleDlg* sched_dlg = 0; KEditLoanWizard* loan_wiz = 0; switch (schedule.type()) { case eMyMoney::Schedule::Type::Bill: case eMyMoney::Schedule::Type::Deposit: case eMyMoney::Schedule::Type::Transfer: sched_dlg = new KEditScheduleDlg(schedule, this); d->m_transactionEditor = sched_dlg->startEdit(); if (d->m_transactionEditor) { KMyMoneyMVCCombo::setSubstringSearchForChildren(sched_dlg, !KMyMoneySettings::stringMatchFromStart()); if (sched_dlg->exec() == QDialog::Accepted) { MyMoneyFileTransaction ft; try { MyMoneySchedule sched = sched_dlg->schedule(); // Check whether the new Schedule Date // is at or before the lastPaymentDate // If it is, ask the user whether to clear the // lastPaymentDate const QDate& next = sched.nextDueDate(); const QDate& last = sched.lastPayment(); if (next.isValid() && last.isValid() && next <= last) { // Entered a date effectively no later // than previous payment. Date would be // updated automatically so we probably // want to clear it. Let's ask the user. if (KMessageBox::questionYesNo(this, QString("") + i18n("You have entered a scheduled transaction date of %1. Because the scheduled transaction was last paid on %2, KMyMoney will automatically adjust the scheduled transaction date to the next date unless the last payment date is reset. Do you want to reset the last payment date?", QLocale().toString(next, QLocale::ShortFormat), QLocale().toString(last, QLocale::ShortFormat)) + QString(""), i18n("Reset Last Payment Date"), KStandardGuiItem::yes(), KStandardGuiItem::no()) == KMessageBox::Yes) { sched.setLastPayment(QDate()); } } MyMoneyFile::instance()->modifySchedule(sched); // delete the editor before we emit the dataChanged() signal from the // engine. Calling this twice in a row does not hurt. deleteTransactionEditor(); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } deleteTransactionEditor(); } delete sched_dlg; break; case eMyMoney::Schedule::Type::LoanPayment: loan_wiz = new KEditLoanWizard(schedule.account(2)); connect(loan_wiz, SIGNAL(newCategory(MyMoneyAccount&)), this, SLOT(slotCategoryNew(MyMoneyAccount&))); connect(loan_wiz, SIGNAL(createPayee(QString,QString&)), this, SLOT(slotPayeeNew(QString,QString&))); if (loan_wiz->exec() == QDialog::Accepted) { MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifySchedule(loan_wiz->schedule()); MyMoneyFile::instance()->modifyAccount(loan_wiz->account()); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } delete loan_wiz; break; case eMyMoney::Schedule::Type::Any: break; } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } } void KMyMoneyApp::slotScheduleDelete() { if (!d->m_selectedSchedule.id().isEmpty()) { MyMoneyFileTransaction ft; try { MyMoneySchedule sched = MyMoneyFile::instance()->schedule(d->m_selectedSchedule.id()); QString msg = i18n("

Are you sure you want to delete the scheduled transaction %1?

", d->m_selectedSchedule.name()); if (sched.type() == eMyMoney::Schedule::Type::LoanPayment) { msg += QString(" "); msg += i18n("In case of loan payments it is currently not possible to recreate the scheduled transaction."); } if (KMessageBox::questionYesNo(this, msg) == KMessageBox::No) return; MyMoneyFile::instance()->removeSchedule(sched); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to remove scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } } void KMyMoneyApp::slotScheduleDuplicate() { // since we may jump here via code, we have to make sure to react only // if the action is enabled if (kmymoney->actionCollection()->action(s_Actions[Action::ScheduleDuplicate])->isEnabled()) { MyMoneySchedule sch = d->m_selectedSchedule; sch.clearId(); sch.setLastPayment(QDate()); sch.setName(i18nc("Copy of scheduled transaction name", "Copy of %1", sch.name())); // make sure that we set a valid next due date if the original next due date is invalid if (!sch.nextDueDate().isValid()) sch.setNextDueDate(QDate::currentDate()); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->addSchedule(sch); ft.commit(); // select the new schedule in the view if (!d->m_selectedSchedule.id().isEmpty()) d->m_myMoneyView->slotScheduleSelected(sch.id()); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to duplicate scheduled transaction: '%1'", d->m_selectedSchedule.name()), e.what()); } } } void KMyMoneyApp::slotScheduleSkip() { if (!d->m_selectedSchedule.id().isEmpty()) { try { MyMoneySchedule schedule = MyMoneyFile::instance()->schedule(d->m_selectedSchedule.id()); skipSchedule(schedule); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unknown scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } } void KMyMoneyApp::skipSchedule(MyMoneySchedule& schedule) { if (!schedule.id().isEmpty()) { try { schedule = MyMoneyFile::instance()->schedule(schedule.id()); if (!schedule.isFinished()) { if (schedule.occurrence() != eMyMoney::Schedule::Occurrence::Once) { QDate next = schedule.nextDueDate(); if (!schedule.isFinished() && (KMessageBox::questionYesNo(this, QString("") + i18n("Do you really want to skip the %1 transaction scheduled for %2?", schedule.name(), QLocale().toString(next, QLocale::ShortFormat)) + QString(""))) == KMessageBox::Yes) { MyMoneyFileTransaction ft; schedule.setLastPayment(next); schedule.setNextDueDate(schedule.nextPayment(next)); MyMoneyFile::instance()->modifySchedule(schedule); ft.commit(); } } } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, QString("") + i18n("Unable to skip scheduled transaction %1.", schedule.name()) + QString(""), e.what()); } } } void KMyMoneyApp::slotScheduleEnter() { if (!d->m_selectedSchedule.id().isEmpty()) { try { MyMoneySchedule schedule = MyMoneyFile::instance()->schedule(d->m_selectedSchedule.id()); enterSchedule(schedule); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unknown scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } } eDialogs::ScheduleResultCode KMyMoneyApp::enterSchedule(MyMoneySchedule& schedule, bool autoEnter, bool extendedKeys) { eDialogs::ScheduleResultCode rc = eDialogs::ScheduleResultCode::Cancel; if (!schedule.id().isEmpty()) { try { schedule = MyMoneyFile::instance()->schedule(schedule.id()); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to enter scheduled transaction '%1'", schedule.name()), e.what()); return rc; } QPointer dlg = new KEnterScheduleDlg(this, schedule); try { QDate origDueDate = schedule.nextDueDate(); dlg->showExtendedKeys(extendedKeys); d->m_transactionEditor = dlg->startEdit(); if (d->m_transactionEditor) { KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart()); MyMoneyTransaction torig, taccepted; d->m_transactionEditor->createTransaction(torig, dlg->transaction(), schedule.transaction().splits().isEmpty() ? MyMoneySplit() : schedule.transaction().splits().front(), true); // force actions to be available no matter what (will be updated according to the state during // slotTransactionsEnter or slotTransactionsCancel) kmymoney->actionCollection()->action(s_Actions[Action::TransactionCancel])->setEnabled(true); kmymoney->actionCollection()->action(s_Actions[Action::TransactionEnter])->setEnabled(true); KConfirmManualEnterDlg::Action action = KConfirmManualEnterDlg::ModifyOnce; if (!autoEnter || !schedule.isFixed()) { for (; dlg != 0;) { rc = eDialogs::ScheduleResultCode::Cancel; if (dlg->exec() == QDialog::Accepted && dlg != 0) { rc = dlg->resultCode(); if (rc == eDialogs::ScheduleResultCode::Enter) { d->m_transactionEditor->createTransaction(taccepted, torig, torig.splits().isEmpty() ? MyMoneySplit() : torig.splits().front(), true); // make sure to suppress comparison of some data: postDate torig.setPostDate(taccepted.postDate()); if (torig != taccepted) { QPointer cdlg = new KConfirmManualEnterDlg(schedule, this); cdlg->loadTransactions(torig, taccepted); if (cdlg->exec() == QDialog::Accepted) { action = cdlg->action(); delete cdlg; break; } delete cdlg; // the user has chosen 'cancel' during confirmation, // we go back to the editor continue; } } else if (rc == eDialogs::ScheduleResultCode::Skip) { slotTransactionsCancel(); skipSchedule(schedule); } else { slotTransactionsCancel(); } } else { if (autoEnter) { if (KMessageBox::warningYesNo(this, i18n("Are you sure you wish to stop this scheduled transaction from being entered into the register?\n\nKMyMoney will prompt you again next time it starts unless you manually enter it later.")) == KMessageBox::No) { // the user has chosen 'No' for the above question, // we go back to the editor continue; } } slotTransactionsCancel(); } break; } } // if we still have the editor around here, the user did not cancel if ((d->m_transactionEditor != 0) && (dlg != 0)) { MyMoneyFileTransaction ft; try { MyMoneyTransaction t; // add the new transaction switch (action) { case KConfirmManualEnterDlg::UseOriginal: // setup widgets with original transaction data d->m_transactionEditor->setTransaction(dlg->transaction(), dlg->transaction().splits().isEmpty() ? MyMoneySplit() : dlg->transaction().splits().front()); // and create a transaction based on that data taccepted = MyMoneyTransaction(); d->m_transactionEditor->createTransaction(taccepted, dlg->transaction(), dlg->transaction().splits().isEmpty() ? MyMoneySplit() : dlg->transaction().splits().front(), true); break; case KConfirmManualEnterDlg::ModifyAlways: torig = taccepted; torig.setPostDate(origDueDate); schedule.setTransaction(torig); break; case KConfirmManualEnterDlg::ModifyOnce: break; } QString newId; connect(d->m_transactionEditor, SIGNAL(balanceWarning(QWidget*,MyMoneyAccount,QString)), d->m_balanceWarning, SLOT(slotShowMessage(QWidget*,MyMoneyAccount,QString))); if (d->m_transactionEditor->enterTransactions(newId, false)) { if (!newId.isEmpty()) { MyMoneyTransaction t = MyMoneyFile::instance()->transaction(newId); schedule.setLastPayment(t.postDate()); } // in case the next due date is invalid, the schedule is finished // we mark it as such by setting the next due date to one day past the end QDate nextDueDate = schedule.nextPayment(origDueDate); if (!nextDueDate.isValid()) { schedule.setNextDueDate(schedule.endDate().addDays(1)); } else { schedule.setNextDueDate(nextDueDate); } MyMoneyFile::instance()->modifySchedule(schedule); rc = eDialogs::ScheduleResultCode::Enter; // delete the editor before we emit the dataChanged() signal from the // engine. Calling this twice in a row does not hurt. deleteTransactionEditor(); ft.commit(); } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to enter scheduled transaction '%1'", schedule.name()), e.what()); } deleteTransactionEditor(); } } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to enter scheduled transaction '%1'", schedule.name()), e.what()); } delete dlg; } return rc; } void KMyMoneyApp::slotPayeeNew(const QString& newnameBase, QString& id) { createPayeeNew(newnameBase, id); } bool KMyMoneyApp::createPayeeNew(const QString& newnameBase, QString& id) { bool doit = true; if (newnameBase != i18n("New Payee")) { // Ask the user if that is what he intended to do? QString msg = QLatin1String("") + i18n("Do you want to add %1 as payer/receiver?", newnameBase) + QLatin1String(""); if (KMessageBox::questionYesNo(this, msg, i18n("New payee/receiver"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "NewPayee") == KMessageBox::No) { doit = false; // we should not keep the 'no' setting because that can confuse people like // I have seen in some usability tests. So we just delete it right away. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("NewPayee")); } } } if (doit) { MyMoneyFileTransaction ft; try { QString newname(newnameBase); // adjust name until a unique name has been created int count = 0; for (;;) { try { MyMoneyFile::instance()->payeeByName(newname); newname = QString("%1 [%2]").arg(newnameBase).arg(++count); } catch (const MyMoneyException &) { break; } } MyMoneyPayee p; p.setName(newname); MyMoneyFile::instance()->addPayee(p); id = p.id(); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to add payee"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); doit = false; } } return doit; } void KMyMoneyApp::slotPayeeNew() { QString id; slotPayeeNew(i18n("New Payee"), id); // the callbacks should have made sure, that the payees view has been // updated already. So we search for the id in the list of items // and select it. emit payeeCreated(id); } bool KMyMoneyApp::payeeInList(const QList& list, const QString& id) const { bool rc = false; QList::const_iterator it_p = list.begin(); while (it_p != list.end()) { if ((*it_p).id() == id) { rc = true; break; } ++it_p; } return rc; } void KMyMoneyApp::slotPayeeDelete() { if (d->m_selectedPayees.isEmpty()) return; // shouldn't happen // get confirmation from user QString prompt; if (d->m_selectedPayees.size() == 1) prompt = i18n("

Do you really want to remove the payee %1?

", d->m_selectedPayees.front().name()); else prompt = i18n("Do you really want to remove all selected payees?"); if (KMessageBox::questionYesNo(this, prompt, i18n("Remove Payee")) == KMessageBox::No) return; payeeReassign(KPayeeReassignDlg::TypeDelete); } void KMyMoneyApp::slotPayeeMerge() { if (d->m_selectedPayees.size() < 1) return; // shouldn't happen if (KMessageBox::questionYesNo(this, i18n("

Do you really want to merge the selected payees?"), i18n("Merge Payees")) == KMessageBox::No) return; if (payeeReassign(KPayeeReassignDlg::TypeMerge)) // clean selection since we just deleted the selected payees slotSelectPayees(QList()); } bool KMyMoneyApp::payeeReassign(int type) { if (!(type >= 0 && type < KPayeeReassignDlg::TypeCount)) return false; MyMoneyFile * file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; try { // create a transaction filter that contains all payees selected for removal MyMoneyTransactionFilter f = MyMoneyTransactionFilter(); for (QList::const_iterator it = d->m_selectedPayees.constBegin(); it != d->m_selectedPayees.constEnd(); ++it) { f.addPayee((*it).id()); } // request a list of all transactions that still use the payees in question QList translist = file->transactionList(f); // qDebug() << "[KPayeesView::slotDeletePayee] " << translist.count() << " transaction still assigned to payees"; // now get a list of all schedules that make use of one of the payees QList all_schedules = file->scheduleList(); QList used_schedules; for (QList::ConstIterator it = all_schedules.constBegin(); it != all_schedules.constEnd(); ++it) { // loop over all splits in the transaction of the schedule for (QList::ConstIterator s_it = (*it).transaction().splits().constBegin(); s_it != (*it).transaction().splits().constEnd(); ++s_it) { // is the payee in the split to be deleted? if (payeeInList(d->m_selectedPayees, (*s_it).payeeId())) { used_schedules.push_back(*it); // remember this schedule break; } } } // qDebug() << "[KPayeesView::slotDeletePayee] " << used_schedules.count() << " schedules use one of the selected payees"; // and a list of all loan accounts that references one of the payees QList allAccounts; QList usedAccounts; file->accountList(allAccounts); foreach (const MyMoneyAccount &account, allAccounts) { if (account.isLoan()) { MyMoneyAccountLoan loanAccount(account); foreach (const MyMoneyPayee &payee, d->m_selectedPayees) { if (loanAccount.hasReferenceTo(payee.id())) { usedAccounts.append(account); } } } } MyMoneyPayee newPayee; bool addToMatchList = false; // if at least one payee is still referenced, we need to reassign its transactions first if (!translist.isEmpty() || !used_schedules.isEmpty() || !usedAccounts.isEmpty()) { // first create list with all non-selected payees QList remainingPayees; if (type == KPayeeReassignDlg::TypeMerge) { remainingPayees = d->m_selectedPayees; } else { remainingPayees = file->payeeList(); QList::iterator it_p; for (it_p = remainingPayees.begin(); it_p != remainingPayees.end();) { if (d->m_selectedPayees.contains(*it_p)) { it_p = remainingPayees.erase(it_p); } else { ++it_p; } } } // show error message if no payees remain if (remainingPayees.isEmpty()) { KMessageBox::sorry(this, i18n("At least one transaction/scheduled transaction or loan account is still referenced by a payee. " "Currently you have all payees selected. However, at least one payee must remain so " "that the transaction/scheduled transaction or loan account can be reassigned.")); return false; } // show transaction reassignment dialog KPayeeReassignDlg * dlg = new KPayeeReassignDlg(static_cast(type), this); KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart()); QString payee_id = dlg->show(remainingPayees); addToMatchList = dlg->addToMatchList(); delete dlg; // and kill the dialog if (payee_id.isEmpty()) return false; // the user aborted the dialog, so let's abort as well // try to get selected payee. If not possible and we are merging payees, // then we create a new one try { newPayee = file->payee(payee_id); } catch (const MyMoneyException &e) { if (type == KPayeeReassignDlg::TypeMerge) { // it's ok to use payee_id for both arguments since the first is const, // so it's garantee not to change its content if (!createPayeeNew(payee_id, payee_id)) return false; // the user aborted the dialog, so let's abort as well newPayee = file->payee(payee_id); } else { return false; } } // TODO : check if we have a report that explicitively uses one of our payees // and issue an appropriate warning try { QList::iterator s_it; // now loop over all transactions and reassign payee for (QList::iterator it = translist.begin(); it != translist.end(); ++it) { // create a copy of the splits list in the transaction QList splits = (*it).splits(); // loop over all splits for (s_it = splits.begin(); s_it != splits.end(); ++s_it) { // if the split is assigned to one of the selected payees, we need to modify it if (payeeInList(d->m_selectedPayees, (*s_it).payeeId())) { (*s_it).setPayeeId(payee_id); // first modify payee in current split // then modify the split in our local copy of the transaction list (*it).modifySplit(*s_it); // this does not modify the list object 'splits'! } } // for - Splits file->modifyTransaction(*it); // modify the transaction in the MyMoney object } // for - Transactions // now loop over all schedules and reassign payees for (QList::iterator it = used_schedules.begin(); it != used_schedules.end(); ++it) { // create copy of transaction in current schedule MyMoneyTransaction trans = (*it).transaction(); // create copy of lists of splits QList splits = trans.splits(); for (s_it = splits.begin(); s_it != splits.end(); ++s_it) { if (payeeInList(d->m_selectedPayees, (*s_it).payeeId())) { (*s_it).setPayeeId(payee_id); trans.modifySplit(*s_it); // does not modify the list object 'splits'! } } // for - Splits // store transaction in current schedule (*it).setTransaction(trans); file->modifySchedule(*it); // modify the schedule in the MyMoney engine } // for - Schedules // reassign the payees in the loans that reference the deleted payees foreach (const MyMoneyAccount &account, usedAccounts) { MyMoneyAccountLoan loanAccount(account); loanAccount.setPayee(payee_id); file->modifyAccount(loanAccount); } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to reassign payee of transaction/split"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } else { // if !translist.isEmpty() if (type == KPayeeReassignDlg::TypeMerge) { KMessageBox::sorry(this, i18n("Nothing to merge."), i18n("Merge Payees")); return false; } } bool ignorecase; QStringList payeeNames; MyMoneyPayee::payeeMatchType matchType = newPayee.matchData(ignorecase, payeeNames); QStringList deletedPayeeNames; // now loop over all selected payees and remove them for (QList::iterator it = d->m_selectedPayees.begin(); it != d->m_selectedPayees.end(); ++it) { if (newPayee.id() != (*it).id()) { if (addToMatchList) { deletedPayeeNames << (*it).name(); } file->removePayee(*it); } } // if we initially have no matching turned on, we just ignore the case (default) if (matchType == MyMoneyPayee::matchDisabled) ignorecase = true; // update the destination payee if this was requested by the user if (addToMatchList && deletedPayeeNames.count() > 0) { // add new names to the list // TODO: it would be cool to somehow shrink the list to make better use // of regular expressions at this point. For now, we leave this task // to the user himeself. QStringList::const_iterator it_n; for (it_n = deletedPayeeNames.constBegin(); it_n != deletedPayeeNames.constEnd(); ++it_n) { if (matchType == MyMoneyPayee::matchKey) { // make sure we really need it and it is not caught by an existing regexp QStringList::const_iterator it_k; for (it_k = payeeNames.constBegin(); it_k != payeeNames.constEnd(); ++it_k) { QRegExp exp(*it_k, ignorecase ? Qt::CaseInsensitive : Qt::CaseSensitive); if (exp.indexIn(*it_n) != -1) break; } if (it_k == payeeNames.constEnd()) payeeNames << QRegExp::escape(*it_n); } else if (payeeNames.contains(*it_n) == 0) payeeNames << QRegExp::escape(*it_n); } // and update the payee in the engine context // make sure to turn on matching for this payee in the right mode newPayee.setMatchData(MyMoneyPayee::matchKey, ignorecase, payeeNames); file->modifyPayee(newPayee); } ft.commit(); // If we just deleted the payees, they sure don't exist anymore slotSelectPayees(QList()); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to remove payee(s)"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } return true; } void KMyMoneyApp::slotTagNew(const QString& newnameBase, QString& id) { bool doit = true; if (newnameBase != i18n("New Tag")) { // Ask the user if that is what he intended to do? QString msg = QString("") + i18n("Do you want to add %1 as tag?", newnameBase) + QString(""); if (KMessageBox::questionYesNo(this, msg, i18n("New tag"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "NewTag") == KMessageBox::No) { doit = false; // we should not keep the 'no' setting because that can confuse people like // I have seen in some usability tests. So we just delete it right away. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("NewTag")); } } } if (doit) { MyMoneyFileTransaction ft; try { QString newname(newnameBase); // adjust name until a unique name has been created int count = 0; for (;;) { try { MyMoneyFile::instance()->tagByName(newname); newname = QString("%1 [%2]").arg(newnameBase).arg(++count); } catch (const MyMoneyException &) { break; } } MyMoneyTag ta; ta.setName(newname); MyMoneyFile::instance()->addTag(ta); id = ta.id(); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to add tag"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } } void KMyMoneyApp::slotTagNew() { QString id; slotTagNew(i18n("New Tag"), id); // the callbacks should have made sure, that the tags view has been // updated already. So we search for the id in the list of items // and select it. emit tagCreated(id); } bool KMyMoneyApp::tagInList(const QList& list, const QString& id) const { bool rc = false; QList::const_iterator it_p = list.begin(); while (it_p != list.end()) { if ((*it_p).id() == id) { rc = true; break; } ++it_p; } return rc; } void KMyMoneyApp::slotTagDelete() { if (d->m_selectedTags.isEmpty()) return; // shouldn't happen MyMoneyFile * file = MyMoneyFile::instance(); // first create list with all non-selected tags QList remainingTags = file->tagList(); QList::iterator it_ta; for (it_ta = remainingTags.begin(); it_ta != remainingTags.end();) { if (d->m_selectedTags.contains(*it_ta)) { it_ta = remainingTags.erase(it_ta); } else { ++it_ta; } } // get confirmation from user QString prompt; if (d->m_selectedTags.size() == 1) prompt = i18n("

Do you really want to remove the tag %1?

", d->m_selectedTags.front().name()); else prompt = i18n("Do you really want to remove all selected tags?"); if (KMessageBox::questionYesNo(this, prompt, i18n("Remove Tag")) == KMessageBox::No) return; MyMoneyFileTransaction ft; try { // create a transaction filter that contains all tags selected for removal MyMoneyTransactionFilter f = MyMoneyTransactionFilter(); for (QList::const_iterator it = d->m_selectedTags.constBegin(); it != d->m_selectedTags.constEnd(); ++it) { f.addTag((*it).id()); } // request a list of all transactions that still use the tags in question QList translist = file->transactionList(f); // qDebug() << "[KTagsView::slotDeleteTag] " << translist.count() << " transaction still assigned to tags"; // now get a list of all schedules that make use of one of the tags QList all_schedules = file->scheduleList(); QList used_schedules; for (QList::ConstIterator it = all_schedules.constBegin(); it != all_schedules.constEnd(); ++it) { // loop over all splits in the transaction of the schedule for (QList::ConstIterator s_it = (*it).transaction().splits().constBegin(); s_it != (*it).transaction().splits().constEnd(); ++s_it) { for (int i = 0; i < (*s_it).tagIdList().size(); i++) { // is the tag in the split to be deleted? if (tagInList(d->m_selectedTags, (*s_it).tagIdList()[i])) { used_schedules.push_back(*it); // remember this schedule break; } } } } // qDebug() << "[KTagsView::slotDeleteTag] " << used_schedules.count() << " schedules use one of the selected tags"; MyMoneyTag newTag; // if at least one tag is still referenced, we need to reassign its transactions first if (!translist.isEmpty() || !used_schedules.isEmpty()) { // show error message if no tags remain //FIXME-ALEX Tags are optional so we can delete all of them and simply delete every tagId from every transaction if (remainingTags.isEmpty()) { KMessageBox::sorry(this, i18n("At least one transaction/scheduled transaction is still referenced by a tag. " "Currently you have all tags selected. However, at least one tag must remain so " "that the transaction/scheduled transaction can be reassigned.")); return; } // show transaction reassignment dialog KTagReassignDlg * dlg = new KTagReassignDlg(this); KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart()); QString tag_id = dlg->show(remainingTags); delete dlg; // and kill the dialog if (tag_id.isEmpty()) //FIXME-ALEX Let the user choose to not reassign a to-be deleted tag to another one. return; // the user aborted the dialog, so let's abort as well newTag = file->tag(tag_id); // TODO : check if we have a report that explicitively uses one of our tags // and issue an appropriate warning try { QList::iterator s_it; // now loop over all transactions and reassign tag for (QList::iterator it = translist.begin(); it != translist.end(); ++it) { // create a copy of the splits list in the transaction QList splits = (*it).splits(); // loop over all splits for (s_it = splits.begin(); s_it != splits.end(); ++s_it) { QList tagIdList = (*s_it).tagIdList(); for (int i = 0; i < tagIdList.size(); i++) { // if the split is assigned to one of the selected tags, we need to modify it if (tagInList(d->m_selectedTags, tagIdList[i])) { tagIdList.removeAt(i); if (tagIdList.indexOf(tag_id) == -1) tagIdList.append(tag_id); i = -1; // restart from the first element } } (*s_it).setTagIdList(tagIdList); // first modify tag list in current split // then modify the split in our local copy of the transaction list (*it).modifySplit(*s_it); // this does not modify the list object 'splits'! } // for - Splits file->modifyTransaction(*it); // modify the transaction in the MyMoney object } // for - Transactions // now loop over all schedules and reassign tags for (QList::iterator it = used_schedules.begin(); it != used_schedules.end(); ++it) { // create copy of transaction in current schedule MyMoneyTransaction trans = (*it).transaction(); // create copy of lists of splits QList splits = trans.splits(); for (s_it = splits.begin(); s_it != splits.end(); ++s_it) { QList tagIdList = (*s_it).tagIdList(); for (int i = 0; i < tagIdList.size(); i++) { if (tagInList(d->m_selectedTags, tagIdList[i])) { tagIdList.removeAt(i); if (tagIdList.indexOf(tag_id) == -1) tagIdList.append(tag_id); i = -1; // restart from the first element } } (*s_it).setTagIdList(tagIdList); trans.modifySplit(*s_it); // does not modify the list object 'splits'! } // for - Splits // store transaction in current schedule (*it).setTransaction(trans); file->modifySchedule(*it); // modify the schedule in the MyMoney engine } // for - Schedules } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to reassign tag of transaction/split"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } // if !translist.isEmpty() // now loop over all selected tags and remove them for (QList::iterator it = d->m_selectedTags.begin(); it != d->m_selectedTags.end(); ++it) { file->removeTag(*it); } ft.commit(); // If we just deleted the tags, they sure don't exist anymore slotSelectTags(QList()); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to remove tag(s)"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } void KMyMoneyApp::slotCurrencyNew() { QString sid = QInputDialog::getText(0, i18n("New currency"), i18n("Enter ISO 4217 code for the new currency")); if (!sid.isEmpty()) { QString id(sid); MyMoneySecurity currency(id, i18n("New currency")); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->addCurrency(currency); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot create new currency. %1", e.what()), i18n("New currency")); } emit currencyCreated(id); } } void KMyMoneyApp::slotCurrencyUpdate(const QString ¤cyId, const QString& currencyName, const QString& currencyTradingSymbol) { MyMoneyFile* file = MyMoneyFile::instance(); try { if (currencyName != d->m_selectedCurrency.name() || currencyTradingSymbol != d->m_selectedCurrency.tradingSymbol()) { MyMoneySecurity currency = file->currency(currencyId); currency.setName(currencyName); currency.setTradingSymbol(currencyTradingSymbol); MyMoneyFileTransaction ft; try { file->modifyCurrency(currency); d->m_selectedCurrency = currency; ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot update currency. %1", e.what()), i18n("Update currency")); } } } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot update currency. %1", e.what()), i18n("Update currency")); } } void KMyMoneyApp::slotCurrencyDelete() { if (!d->m_selectedCurrency.id().isEmpty()) { MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->removeCurrency(d->m_selectedCurrency); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot delete currency %1. %2", d->m_selectedCurrency.name(), e.what()), i18n("Delete currency")); } } } void KMyMoneyApp::slotCurrencySetBase() { if (!d->m_selectedCurrency.id().isEmpty()) { if (d->m_selectedCurrency.id() != MyMoneyFile::instance()->baseCurrency().id()) { MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->setBaseCurrency(d->m_selectedCurrency); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot set %1 as base currency: %2", d->m_selectedCurrency.name(), e.what()), i18n("Set base currency")); } } } } void KMyMoneyApp::slotBudgetNew() { QDate date = QDate::currentDate(); date.setDate(date.year(), KMyMoneyGlobalSettings::firstFiscalMonth(), KMyMoneyGlobalSettings::firstFiscalDay()); QString newname = i18n("Budget %1", date.year()); MyMoneyBudget budget; // make sure we have a unique name try { int i = 1; // Exception thrown when the name is not found while (1) { MyMoneyFile::instance()->budgetByName(newname); newname = i18n("Budget %1 %2", date.year(), i++); } } catch (const MyMoneyException &) { // all ok, the name is unique } MyMoneyFileTransaction ft; try { budget.setName(newname); budget.setBudgetStart(date); MyMoneyFile::instance()->addBudget(budget); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to add budget: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } void KMyMoneyApp::slotBudgetDelete() { if (d->m_selectedBudgets.isEmpty()) return; // shouldn't happen MyMoneyFile * file = MyMoneyFile::instance(); // get confirmation from user QString prompt; if (d->m_selectedBudgets.size() == 1) prompt = i18n("

Do you really want to remove the budget %1?

", d->m_selectedBudgets.front().name()); else prompt = i18n("Do you really want to remove all selected budgets?"); if (KMessageBox::questionYesNo(this, prompt, i18n("Remove Budget")) == KMessageBox::No) return; MyMoneyFileTransaction ft; try { // now loop over all selected budgets and remove them for (QList::iterator it = d->m_selectedBudgets.begin(); it != d->m_selectedBudgets.end(); ++it) { file->removeBudget(*it); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to remove budget: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } void KMyMoneyApp::slotBudgetCopy() { if (d->m_selectedBudgets.size() == 1) { MyMoneyFileTransaction ft; try { MyMoneyBudget budget = d->m_selectedBudgets.first(); budget.clearId(); budget.setName(i18n("Copy of %1", budget.name())); MyMoneyFile::instance()->addBudget(budget); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to add budget: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } } void KMyMoneyApp::slotBudgetChangeYear() { if (d->m_selectedBudgets.size() == 1) { QStringList years; int current = 0; bool haveCurrent = false; MyMoneyBudget budget = *(d->m_selectedBudgets.begin()); for (int i = (QDate::currentDate().year() - 3); i < (QDate::currentDate().year() + 5); ++i) { years << QString("%1").arg(i); if (i == budget.budgetStart().year()) { haveCurrent = true; } if (!haveCurrent) ++current; } if (!haveCurrent) current = 0; bool ok = false; QString yearString = QInputDialog::getItem(this, i18n("Select year"), i18n("Budget year"), years, current, false, &ok); if (ok) { int year = yearString.toInt(0, 0); QDate newYear = QDate(year, budget.budgetStart().month(), budget.budgetStart().day()); if (newYear != budget.budgetStart()) { MyMoneyFileTransaction ft; try { budget.setBudgetStart(newYear); MyMoneyFile::instance()->modifyBudget(budget); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to modify budget: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } } } } void KMyMoneyApp::slotBudgetForecast() { if (d->m_selectedBudgets.size() == 1) { MyMoneyFileTransaction ft; try { MyMoneyBudget budget = d->m_selectedBudgets.first(); bool calcBudget = budget.getaccounts().count() == 0; if (!calcBudget) { if (KMessageBox::warningContinueCancel(0, i18n("The current budget already contains data. Continuing will replace all current values of this budget."), i18nc("Warning message box", "Warning")) == KMessageBox::Continue) calcBudget = true; } if (calcBudget) { QDate historyStart; QDate historyEnd; QDate budgetStart; QDate budgetEnd; budgetStart = budget.budgetStart(); budgetEnd = budgetStart.addYears(1).addDays(-1); historyStart = budgetStart.addYears(-1); historyEnd = budgetEnd.addYears(-1); MyMoneyForecast forecast = KMyMoneyGlobalSettings::forecast(); forecast.createBudget(budget, historyStart, historyEnd, budgetStart, budgetEnd, true); MyMoneyFile::instance()->modifyBudget(budget); ft.commit(); } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to modify budget: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } } void KMyMoneyApp::slotNewFeature() { } void KMyMoneyApp::slotTransactionsDelete() { // since we may jump here via code, we have to make sure to react only // if the action is enabled if (!kmymoney->actionCollection()->action(s_Actions[Action::TransactionDelete])->isEnabled()) return; if (d->m_selectedTransactions.isEmpty()) return; if (d->m_selectedTransactions.warnLevel() == 1) { if (KMessageBox::warningContinueCancel(0, i18n("At least one split of the selected transactions has been reconciled. " "Do you wish to delete the transactions anyway?"), i18n("Transaction already reconciled")) == KMessageBox::Cancel) return; } QString msg = i18np("Do you really want to delete the selected transaction?", "Do you really want to delete all %1 selected transactions?", d->m_selectedTransactions.count()); if (KMessageBox::questionYesNo(this, msg, i18n("Delete transaction")) == KMessageBox::Yes) { KMSTATUS(i18n("Deleting transactions")); doDeleteTransactions(); } } void KMyMoneyApp::slotTransactionDuplicate() { // since we may jump here via code, we have to make sure to react only // if the action is enabled if (kmymoney->actionCollection()->action(s_Actions[Action::TransactionDuplicate])->isEnabled()) { KMyMoneyRegister::SelectedTransactions list = d->m_selectedTransactions; KMyMoneyRegister::SelectedTransactions::iterator it_t; int i = 0; int cnt = d->m_selectedTransactions.count(); KMSTATUS(i18n("Duplicating transactions")); slotStatusProgressBar(0, cnt); MyMoneyFileTransaction ft; MyMoneyTransaction lt; try { for (it_t = list.begin(); it_t != list.end(); ++it_t) { MyMoneyTransaction t = (*it_t).transaction(); QList::iterator it_s; // wipe out any reconciliation information for (it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) { (*it_s).setReconcileFlag(eMyMoney::Split::State::NotReconciled); (*it_s).setReconcileDate(QDate()); (*it_s).setBankID(QString()); } // clear invalid data t.setEntryDate(QDate()); t.clearId(); // and set the post date to today t.setPostDate(QDate::currentDate()); MyMoneyFile::instance()->addTransaction(t); lt = t; slotStatusProgressBar(i++, 0); } ft.commit(); // select the new transaction in the ledger if (!d->m_selectedAccount.id().isEmpty()) d->m_myMoneyView->slotLedgerSelected(d->m_selectedAccount.id(), lt.id()); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to duplicate transaction(s): %1, thrown in %2:%3", e.what(), e.file(), e.line())); } // switch off the progress bar slotStatusProgressBar(-1, -1); } } void KMyMoneyApp::doDeleteTransactions() { KMyMoneyRegister::SelectedTransactions list = d->m_selectedTransactions; KMyMoneyRegister::SelectedTransactions::iterator it_t; int cnt = list.count(); int i = 0; slotStatusProgressBar(0, cnt); MyMoneyFileTransaction ft; MyMoneyFile* file = MyMoneyFile::instance(); try { it_t = list.begin(); while (it_t != list.end()) { // only remove those transactions that do not reference a closed account if (!file->referencesClosedAccount((*it_t).transaction())) { file->removeTransaction((*it_t).transaction()); // remove all those references in the list of selected transactions // that refer to the same transaction we just removed so that we // will not be caught by an exception later on (see bko #285310) KMyMoneyRegister::SelectedTransactions::iterator it_td = it_t; ++it_td; while (it_td != list.end()) { if ((*it_t).transaction().id() == (*it_td).transaction().id()) { it_td = list.erase(it_td); i++; // bump count of deleted transactions } else { ++it_td; } } } // need to ensure "nextCheckNumber" is still correct MyMoneyAccount acc = file->account((*it_t).split().accountId()); // the "lastNumberUsed" might have been the txn number deleted // so adjust it QString deletedNum = (*it_t).split().number(); // decrement deletedNum and set new "lastNumberUsed" QString num = KMyMoneyUtils::getAdjacentNumber(deletedNum, -1); acc.setValue("lastNumberUsed", num); file->modifyAccount(acc); list.erase(it_t); it_t = list.begin(); slotStatusProgressBar(i++, 0); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to delete transaction(s): %1, thrown in %2:%3", e.what(), e.file(), e.line())); } slotStatusProgressBar(-1, -1); } void KMyMoneyApp::slotTransactionsNew() { // since we jump here via code, we have to make sure to react only // if the action is enabled if (kmymoney->actionCollection()->action(s_Actions[Action::TransactionNew])->isEnabled()) { if (d->m_myMoneyView->createNewTransaction()) { d->m_transactionEditor = d->m_myMoneyView->startEdit(d->m_selectedTransactions); if (d->m_transactionEditor) { KMyMoneyMVCCombo::setSubstringSearchForChildren(d->m_myMoneyView, !KMyMoneySettings::stringMatchFromStart()); KMyMoneyPayeeCombo* payeeEdit = dynamic_cast(d->m_transactionEditor->haveWidget("payee")); if (payeeEdit && !d->m_lastPayeeEnteredId.isEmpty()) { // in case we entered a new transaction before and used a payee, // we reuse it here. Save the text to the edit widget, select it // so that hitting any character will start entering another payee. payeeEdit->setSelectedItem(d->m_lastPayeeEnteredId); payeeEdit->lineEdit()->selectAll(); } if (d->m_transactionEditor) { connect(d->m_transactionEditor, SIGNAL(statusProgress(int,int)), this, SLOT(slotStatusProgressBar(int,int))); connect(d->m_transactionEditor, SIGNAL(statusMsg(QString)), this, SLOT(slotStatusMsg(QString))); connect(d->m_transactionEditor, SIGNAL(scheduleTransaction(MyMoneyTransaction,eMyMoney::Schedule::Occurrence)), this, SLOT(slotScheduleNew(MyMoneyTransaction,eMyMoney::Schedule::Occurrence))); } slotUpdateActions(); } } } } void KMyMoneyApp::slotTransactionsEdit() { // qDebug("KMyMoneyApp::slotTransactionsEdit()"); // since we jump here via code, we have to make sure to react only // if the action is enabled if (kmymoney->actionCollection()->action(s_Actions[Action::TransactionEdit])->isEnabled()) { // as soon as we edit a transaction, we don't remember the last payee entered d->m_lastPayeeEnteredId.clear(); d->m_transactionEditor = d->m_myMoneyView->startEdit(d->m_selectedTransactions); KMyMoneyMVCCombo::setSubstringSearchForChildren(d->m_myMoneyView, !KMyMoneySettings::stringMatchFromStart()); slotUpdateActions(); } } void KMyMoneyApp::deleteTransactionEditor() { // make sure, we don't use the transaction editor pointer // anymore from now on TransactionEditor* p = d->m_transactionEditor; d->m_transactionEditor = 0; delete p; } void KMyMoneyApp::slotTransactionsEditSplits() { // since we jump here via code, we have to make sure to react only // if the action is enabled if (kmymoney->actionCollection()->action(s_Actions[Action::TransactionEditSplits])->isEnabled()) { // as soon as we edit a transaction, we don't remember the last payee entered d->m_lastPayeeEnteredId.clear(); d->m_transactionEditor = d->m_myMoneyView->startEdit(d->m_selectedTransactions); slotUpdateActions(); if (d->m_transactionEditor) { KMyMoneyMVCCombo::setSubstringSearchForChildren(d->m_myMoneyView, !KMyMoneySettings::stringMatchFromStart()); if (d->m_transactionEditor->slotEditSplits() == QDialog::Accepted) { MyMoneyFileTransaction ft; try { QString id; connect(d->m_transactionEditor, SIGNAL(balanceWarning(QWidget*,MyMoneyAccount,QString)), d->m_balanceWarning, SLOT(slotShowMessage(QWidget*,MyMoneyAccount,QString))); d->m_transactionEditor->enterTransactions(id); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to modify transaction: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } } deleteTransactionEditor(); slotUpdateActions(); } } void KMyMoneyApp::slotTransactionsCancel() { // since we jump here via code, we have to make sure to react only // if the action is enabled if (kmymoney->actionCollection()->action(s_Actions[Action::TransactionCancel])->isEnabled()) { // make sure, we block the enter function actionCollection()->action(s_Actions[Action::TransactionEnter])->setEnabled(false); // qDebug("KMyMoneyApp::slotTransactionsCancel"); deleteTransactionEditor(); slotUpdateActions(); } } void KMyMoneyApp::slotTransactionsEnter() { // since we jump here via code, we have to make sure to react only // if the action is enabled if (kmymoney->actionCollection()->action(s_Actions[Action::TransactionEnter])->isEnabled()) { // disable the action while we process it to make sure it's processed only once since // d->m_transactionEditor->enterTransactions(newId) will run QCoreApplication::processEvents // we could end up here twice which will cause a crash slotUpdateActions() will enable the action again kmymoney->actionCollection()->action(s_Actions[Action::TransactionEnter])->setEnabled(false); if (d->m_transactionEditor) { QString accountId = d->m_selectedAccount.id(); QString newId; connect(d->m_transactionEditor, SIGNAL(balanceWarning(QWidget*,MyMoneyAccount,QString)), d->m_balanceWarning, SLOT(slotShowMessage(QWidget*,MyMoneyAccount,QString))); if (d->m_transactionEditor->enterTransactions(newId)) { KMyMoneyPayeeCombo* payeeEdit = dynamic_cast(d->m_transactionEditor->haveWidget("payee")); if (payeeEdit && !newId.isEmpty()) { d->m_lastPayeeEnteredId = payeeEdit->selectedItem(); } deleteTransactionEditor(); } if (!newId.isEmpty()) { d->m_myMoneyView->slotLedgerSelected(accountId, newId); } } slotUpdateActions(); } } void KMyMoneyApp::slotTransactionsCancelOrEnter(bool& okToSelect) { static bool oneTime = false; if (!oneTime) { oneTime = true; QString dontShowAgain = "CancelOrEditTransaction"; // qDebug("KMyMoneyApp::slotCancelOrEndEdit"); if (d->m_transactionEditor) { if (KMyMoneyGlobalSettings::focusChangeIsEnter() && kmymoney->actionCollection()->action(s_Actions[Action::TransactionEnter])->isEnabled()) { slotTransactionsEnter(); if (d->m_transactionEditor) { // if at this stage the editor is still there that means that entering the transaction was cancelled // for example by pressing cancel on the exchange rate editor so we must stay in edit mode okToSelect = false; } } else { // okToSelect is preset to true if a cancel of the dialog is useful and false if it is not int rc; KGuiItem noGuiItem = KStandardGuiItem::save(); KGuiItem yesGuiItem = KStandardGuiItem::discard(); KGuiItem cancelGuiItem = KStandardGuiItem::cont(); // if the transaction can't be entered make sure that it can't be entered by pressing no either if (!kmymoney->actionCollection()->action(s_Actions[Action::TransactionEnter])->isEnabled()) { noGuiItem.setEnabled(false); noGuiItem.setToolTip(kmymoney->actionCollection()->action(s_Actions[Action::TransactionEnter])->toolTip()); } if (okToSelect == true) { rc = KMessageBox::warningYesNoCancel(0, i18n("

Please select what you want to do: discard the changes, save the changes or continue to edit the transaction.

You can also set an option to save the transaction automatically when e.g. selecting another transaction.

"), i18n("End transaction edit"), yesGuiItem, noGuiItem, cancelGuiItem, dontShowAgain); } else { rc = KMessageBox::warningYesNo(0, i18n("

Please select what you want to do: discard the changes, save the changes or continue to edit the transaction.

You can also set an option to save the transaction automatically when e.g. selecting another transaction.

"), i18n("End transaction edit"), yesGuiItem, noGuiItem, dontShowAgain); } switch (rc) { case KMessageBox::Yes: slotTransactionsCancel(); break; case KMessageBox::No: slotTransactionsEnter(); // make sure that we'll see this message the next time no matter // if the user has chosen the 'Don't show again' checkbox KMessageBox::enableMessage(dontShowAgain); if (d->m_transactionEditor) { // if at this stage the editor is still there that means that entering the transaction was cancelled // for example by pressing cancel on the exchange rate editor so we must stay in edit mode okToSelect = false; } break; case KMessageBox::Cancel: // make sure that we'll see this message the next time no matter // if the user has chosen the 'Don't show again' checkbox KMessageBox::enableMessage(dontShowAgain); okToSelect = false; break; } } } oneTime = false; } } void KMyMoneyApp::slotToggleReconciliationFlag() { markTransaction(eMyMoney::Split::State::Unknown); } void KMyMoneyApp::slotMarkTransactionCleared() { markTransaction(eMyMoney::Split::State::Cleared); } void KMyMoneyApp::slotMarkTransactionReconciled() { markTransaction(eMyMoney::Split::State::Reconciled); } void KMyMoneyApp::slotMarkTransactionNotReconciled() { markTransaction(eMyMoney::Split::State::NotReconciled); } void KMyMoneyApp::markTransaction(eMyMoney::Split::State flag) { KMyMoneyRegister::SelectedTransactions list = d->m_selectedTransactions; KMyMoneyRegister::SelectedTransactions::const_iterator it_t; int cnt = list.count(); int i = 0; slotStatusProgressBar(0, cnt); MyMoneyFileTransaction ft; try { for (it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) { // turn on signals before we modify the last entry in the list cnt--; MyMoneyFile::instance()->blockSignals(cnt != 0); // get a fresh copy MyMoneyTransaction t = MyMoneyFile::instance()->transaction((*it_t).transaction().id()); MyMoneySplit sp = t.splitById((*it_t).split().id()); if (sp.reconcileFlag() != flag) { if (flag == eMyMoney::Split::State::Unknown) { if (d->m_reconciliationAccount.id().isEmpty()) { // in normal mode we cycle through all states switch (sp.reconcileFlag()) { case eMyMoney::Split::State::NotReconciled: sp.setReconcileFlag(eMyMoney::Split::State::Cleared); break; case eMyMoney::Split::State::Cleared: sp.setReconcileFlag(eMyMoney::Split::State::Reconciled); break; case eMyMoney::Split::State::Reconciled: sp.setReconcileFlag(eMyMoney::Split::State::NotReconciled); break; default: break; } } else { // in reconciliation mode we skip the reconciled state switch (sp.reconcileFlag()) { case eMyMoney::Split::State::NotReconciled: sp.setReconcileFlag(eMyMoney::Split::State::Cleared); break; case eMyMoney::Split::State::Cleared: sp.setReconcileFlag(eMyMoney::Split::State::NotReconciled); break; default: break; } } } else { sp.setReconcileFlag(flag); } t.modifySplit(sp); MyMoneyFile::instance()->modifyTransaction(t); } slotStatusProgressBar(i++, 0); } slotStatusProgressBar(-1, -1); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to modify transaction: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } void KMyMoneyApp::slotTransactionsAccept() { KMyMoneyRegister::SelectedTransactions list = d->m_selectedTransactions; KMyMoneyRegister::SelectedTransactions::const_iterator it_t; int cnt = list.count(); int i = 0; slotStatusProgressBar(0, cnt); MyMoneyFileTransaction ft; try { for (it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) { // reload transaction in case it got changed during the course of this loop MyMoneyTransaction t = MyMoneyFile::instance()->transaction((*it_t).transaction().id()); if (t.isImported()) { t.setImported(false); if (!d->m_selectedAccount.id().isEmpty()) { QList list = t.splits(); QList::const_iterator it_s; for (it_s = list.constBegin(); it_s != list.constEnd(); ++it_s) { if ((*it_s).accountId() == d->m_selectedAccount.id()) { if ((*it_s).reconcileFlag() == eMyMoney::Split::State::NotReconciled) { MyMoneySplit s = (*it_s); s.setReconcileFlag(eMyMoney::Split::State::Cleared); t.modifySplit(s); } } } } MyMoneyFile::instance()->modifyTransaction(t); } if ((*it_t).split().isMatched()) { // reload split in case it got changed during the course of this loop MyMoneySplit s = t.splitById((*it_t).split().id()); TransactionMatcher matcher(d->m_selectedAccount); matcher.accept(t, s); } slotStatusProgressBar(i++, 0); } slotStatusProgressBar(-1, -1); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to accept transaction: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } void KMyMoneyApp::slotTransactionGotoAccount() { if (!d->m_accountGoto.isEmpty()) { try { QString transactionId; if (d->m_selectedTransactions.count() == 1) { transactionId = d->m_selectedTransactions[0].transaction().id(); } // make sure to pass a copy, as d->myMoneyView->slotLedgerSelected() overrides // d->m_accountGoto while calling slotUpdateActions() QString accountId = d->m_accountGoto; d->m_myMoneyView->slotLedgerSelected(accountId, transactionId); } catch (const MyMoneyException &) { } } } void KMyMoneyApp::slotTransactionGotoPayee() { if (!d->m_payeeGoto.isEmpty()) { try { QString transactionId; if (d->m_selectedTransactions.count() == 1) { transactionId = d->m_selectedTransactions[0].transaction().id(); } // make sure to pass copies, as d->myMoneyView->slotPayeeSelected() overrides // d->m_payeeGoto and d->m_selectedAccount while calling slotUpdateActions() QString payeeId = d->m_payeeGoto; QString accountId = d->m_selectedAccount.id(); d->m_myMoneyView->slotPayeeSelected(payeeId, accountId, transactionId); } catch (const MyMoneyException &) { } } } void KMyMoneyApp::slotTransactionCreateSchedule() { if (d->m_selectedTransactions.count() == 1) { // make sure to have the current selected split as first split in the schedule MyMoneyTransaction t = d->m_selectedTransactions[0].transaction(); MyMoneySplit s = d->m_selectedTransactions[0].split(); QString splitId = s.id(); s.clearId(); s.setReconcileFlag(eMyMoney::Split::State::NotReconciled); s.setReconcileDate(QDate()); t.removeSplits(); t.addSplit(s); const QList& splits = d->m_selectedTransactions[0].transaction().splits(); QList::const_iterator it_s; for (it_s = splits.begin(); it_s != splits.end(); ++it_s) { if ((*it_s).id() != splitId) { MyMoneySplit s0 = (*it_s); s0.clearId(); s0.setReconcileFlag(eMyMoney::Split::State::NotReconciled); s0.setReconcileDate(QDate()); t.addSplit(s0); } } slotScheduleNew(t); } } void KMyMoneyApp::slotTransactionAssignNumber() { if (d->m_transactionEditor) d->m_transactionEditor->assignNextNumber(); } void KMyMoneyApp::slotTransactionCombine() { qDebug("slotTransactionCombine() not implemented yet"); } void KMyMoneyApp::slotTransactionCopySplits() { MyMoneyFile* file = MyMoneyFile::instance(); if (d->m_selectedTransactions.count() >= 2) { int singleSplitTransactions = 0; int multipleSplitTransactions = 0; KMyMoneyRegister::SelectedTransaction selectedSourceTransaction; foreach (const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) { switch (st.transaction().splitCount()) { case 0: break; case 1: singleSplitTransactions++; break; default: selectedSourceTransaction = st; multipleSplitTransactions++; break; } } if (singleSplitTransactions > 0 && multipleSplitTransactions == 1) { MyMoneyFileTransaction ft; try { const MyMoneyTransaction& sourceTransaction = selectedSourceTransaction.transaction(); const MyMoneySplit& sourceSplit = selectedSourceTransaction.split(); foreach (const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) { MyMoneyTransaction t = st.transaction(); // don't process the source transaction if (sourceTransaction.id() == t.id()) { continue; } const MyMoneySplit& baseSplit = st.split(); if (t.splitCount() == 1) { foreach (const MyMoneySplit& split, sourceTransaction.splits()) { // Don't copy the source split, as we already have that // as part of the destination transaction if (split.id() == sourceSplit.id()) { continue; } MyMoneySplit sp(split); // clear the ID and reconciliation state sp.clearId(); sp.setReconcileFlag(eMyMoney::Split::State::NotReconciled); sp.setReconcileDate(QDate()); // in case it is a simple transaction consisting of two splits, // we can adjust the share and value part of the second split we // just created. We need to keep a possible price in mind in case // of different currencies if (sourceTransaction.splitCount() == 2) { sp.setValue(-baseSplit.value()); sp.setShares(-(baseSplit.shares() * baseSplit.price())); } t.addSplit(sp); } file->modifyTransaction(t); } } ft.commit(); } catch (const MyMoneyException &) { qDebug() << "transactionCopySplits() failed"; } } } } void KMyMoneyApp::slotMoveToAccount(const QString& id) { // close the menu, if it is still open QWidget* w = factory()->container("transaction_context_menu", this); if (w && w->isVisible()) { w->close(); } if (!d->m_selectedTransactions.isEmpty()) { MyMoneyFileTransaction ft; try { KMyMoneyRegister::SelectedTransactions::const_iterator it_t; for (it_t = d->m_selectedTransactions.constBegin(); it_t != d->m_selectedTransactions.constEnd(); ++it_t) { if (d->m_selectedAccount.accountType() == eMyMoney::Account::Type::Investment) { d->moveInvestmentTransaction(d->m_selectedAccount.id(), id, (*it_t).transaction()); } else { QList::const_iterator it_s; bool changed = false; MyMoneyTransaction t = (*it_t).transaction(); for (it_s = (*it_t).transaction().splits().constBegin(); it_s != (*it_t).transaction().splits().constEnd(); ++it_s) { if ((*it_s).accountId() == d->m_selectedAccount.id()) { MyMoneySplit s = (*it_s); s.setAccountId(id); t.modifySplit(s); changed = true; } } if (changed) { MyMoneyFile::instance()->modifyTransaction(t); } } } ft.commit(); } catch (const MyMoneyException &) { } } } // move a stock transaction from one investment account to another void KMyMoneyApp::Private::moveInvestmentTransaction(const QString& /*fromId*/, const QString& toId, const MyMoneyTransaction& tx) { MyMoneyAccount toInvAcc = MyMoneyFile::instance()->account(toId); MyMoneyTransaction t(tx); // first determine which stock we are dealing with. // fortunately, investment transactions have only one stock involved QString stockAccountId; QString stockSecurityId; MyMoneySplit s; for (QList::const_iterator it_s = t.splits().constBegin(); it_s != t.splits().constEnd(); ++it_s) { stockAccountId = (*it_s).accountId(); stockSecurityId = MyMoneyFile::instance()->account(stockAccountId).currencyId(); if (!MyMoneyFile::instance()->security(stockSecurityId).isCurrency()) { s = *it_s; break; } } // Now check the target investment account to see if it // contains a stock with this id QString newStockAccountId; QStringList accountList = toInvAcc.accountList(); for (QStringList::const_iterator it_a = accountList.constBegin(); it_a != accountList.constEnd(); ++it_a) { if (MyMoneyFile::instance()->account((*it_a)).currencyId() == stockSecurityId) { newStockAccountId = (*it_a); break; } } // if it doesn't exist, we need to add it as a copy of the old one // no 'copyAccount()' function?? if (newStockAccountId.isEmpty()) { MyMoneyAccount stockAccount = MyMoneyFile::instance()->account(stockAccountId); MyMoneyAccount newStock; newStock.setName(stockAccount.name()); newStock.setNumber(stockAccount.number()); newStock.setDescription(stockAccount.description()); newStock.setInstitutionId(stockAccount.institutionId()); newStock.setOpeningDate(stockAccount.openingDate()); newStock.setAccountType(stockAccount.accountType()); newStock.setCurrencyId(stockAccount.currencyId()); newStock.setClosed(stockAccount.isClosed()); MyMoneyFile::instance()->addAccount(newStock, toInvAcc); newStockAccountId = newStock.id(); } // now update the split and the transaction s.setAccountId(newStockAccountId); t.modifySplit(s); MyMoneyFile::instance()->modifyTransaction(t); } void KMyMoneyApp::slotUpdateMoveToAccountMenu() { createTransactionMoveMenu(); // in case we were not able to create the selector, we // better get out of here. Anything else would cause // a crash later on (accountSet.load) if (!d->m_moveToAccountSelector) return; if (!d->m_selectedAccount.id().isEmpty()) { AccountSet accountSet; if (d->m_selectedAccount.accountType() == eMyMoney::Account::Type::Investment) { accountSet.addAccountType(eMyMoney::Account::Type::Investment); } else if (d->m_selectedAccount.isAssetLiability()) { accountSet.addAccountType(eMyMoney::Account::Type::Checkings); accountSet.addAccountType(eMyMoney::Account::Type::Savings); accountSet.addAccountType(eMyMoney::Account::Type::Cash); accountSet.addAccountType(eMyMoney::Account::Type::AssetLoan); accountSet.addAccountType(eMyMoney::Account::Type::CertificateDep); accountSet.addAccountType(eMyMoney::Account::Type::MoneyMarket); accountSet.addAccountType(eMyMoney::Account::Type::Asset); accountSet.addAccountType(eMyMoney::Account::Type::Currency); accountSet.addAccountType(eMyMoney::Account::Type::CreditCard); accountSet.addAccountType(eMyMoney::Account::Type::Loan); accountSet.addAccountType(eMyMoney::Account::Type::Liability); } else if (d->m_selectedAccount.isIncomeExpense()) { accountSet.addAccountType(eMyMoney::Account::Type::Income); accountSet.addAccountType(eMyMoney::Account::Type::Expense); } accountSet.load(d->m_moveToAccountSelector); // remove those accounts that we currently reference KMyMoneyRegister::SelectedTransactions::const_iterator it_t; for (it_t = d->m_selectedTransactions.constBegin(); it_t != d->m_selectedTransactions.constEnd(); ++it_t) { QList::const_iterator it_s; for (it_s = (*it_t).transaction().splits().constBegin(); it_s != (*it_t).transaction().splits().constEnd(); ++it_s) { d->m_moveToAccountSelector->removeItem((*it_s).accountId()); } } // remove those accounts from the list that are denominated // in a different currency QStringList list = d->m_moveToAccountSelector->accountList(); QList::const_iterator it_a; for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { MyMoneyAccount acc = MyMoneyFile::instance()->account(*it_a); if (acc.currencyId() != d->m_selectedAccount.currencyId()) d->m_moveToAccountSelector->removeItem((*it_a)); } } } void KMyMoneyApp::slotTransactionMatch() { // if the menu action is retrieved it can contain an '&' character for the accelerator causing the comparison to fail if not removed QString transactionActionText = actionCollection()->action(s_Actions[Action::TransactionMatch])->text(); transactionActionText.remove('&'); if (transactionActionText == i18nc("Button text for match transaction", "Match")) transactionMatch(); else transactionUnmatch(); } void KMyMoneyApp::transactionUnmatch() { KMyMoneyRegister::SelectedTransactions::const_iterator it; MyMoneyFileTransaction ft; try { for (it = d->m_selectedTransactions.constBegin(); it != d->m_selectedTransactions.constEnd(); ++it) { if ((*it).split().isMatched()) { TransactionMatcher matcher(d->m_selectedAccount); matcher.unmatch((*it).transaction(), (*it).split()); } } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to unmatch the selected transactions"), e.what()); } } void KMyMoneyApp::transactionMatch() { if (d->m_selectedTransactions.count() != 2) return; MyMoneyTransaction startMatchTransaction; MyMoneyTransaction endMatchTransaction; MyMoneySplit startSplit; MyMoneySplit endSplit; KMyMoneyRegister::SelectedTransactions::const_iterator it; KMyMoneyRegister::SelectedTransactions toBeDeleted; for (it = d->m_selectedTransactions.constBegin(); it != d->m_selectedTransactions.constEnd(); ++it) { if ((*it).transaction().isImported()) { if (endMatchTransaction.id().isEmpty()) { endMatchTransaction = (*it).transaction(); endSplit = (*it).split(); toBeDeleted << *it; } else { //This is a second imported transaction, we still want to merge startMatchTransaction = (*it).transaction(); startSplit = (*it).split(); } } else if (!(*it).split().isMatched()) { if (startMatchTransaction.id().isEmpty()) { startMatchTransaction = (*it).transaction(); startSplit = (*it).split(); } else { endMatchTransaction = (*it).transaction(); endSplit = (*it).split(); toBeDeleted << *it; } } } #if 0 KMergeTransactionsDlg dlg(d->m_selectedAccount); dlg.addTransaction(startMatchTransaction); dlg.addTransaction(endMatchTransaction); if (dlg.exec() == QDialog::Accepted) #endif { MyMoneyFileTransaction ft; try { if (startMatchTransaction.id().isEmpty()) throw MYMONEYEXCEPTION(i18n("No manually entered transaction selected for matching")); if (endMatchTransaction.id().isEmpty()) throw MYMONEYEXCEPTION(i18n("No imported transaction selected for matching")); TransactionMatcher matcher(d->m_selectedAccount); matcher.match(startMatchTransaction, startSplit, endMatchTransaction, endSplit, true); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to match the selected transactions"), e.what()); } } } void KMyMoneyApp::showContextMenu(const QString& containerName) { QWidget* w = factory()->container(containerName, this); QMenu *menu = dynamic_cast(w); if (menu) menu->exec(QCursor::pos()); else qDebug("menu '%s' not found: w = %p, menu = %p", qPrintable(containerName), w, menu); } void KMyMoneyApp::slotShowTransactionContextMenu() { if (d->m_selectedTransactions.isEmpty() && d->m_selectedSchedule != MyMoneySchedule()) { showContextMenu("schedule_context_menu"); } else { showContextMenu("transaction_context_menu"); } } void KMyMoneyApp::slotShowInvestmentContextMenu() { showContextMenu("investment_context_menu"); } void KMyMoneyApp::slotShowScheduleContextMenu() { showContextMenu("schedule_context_menu"); } void KMyMoneyApp::slotShowAccountContextMenu(const MyMoneyObject& obj) { // qDebug("KMyMoneyApp::slotShowAccountContextMenu"); if (typeid(obj) != typeid(MyMoneyAccount)) return; const MyMoneyAccount& acc = dynamic_cast(obj); // if the selected account is actually a stock account, we // call the right slot instead if (acc.isInvest()) { showContextMenu("investment_context_menu"); } else if (acc.isIncomeExpense()) { showContextMenu("category_context_menu"); } else { showContextMenu("account_context_menu"); } } void KMyMoneyApp::slotShowInstitutionContextMenu(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyInstitution)) return; showContextMenu("institution_context_menu"); } void KMyMoneyApp::slotShowPayeeContextMenu() { showContextMenu("payee_context_menu"); } void KMyMoneyApp::slotShowTagContextMenu() { showContextMenu("tag_context_menu"); } void KMyMoneyApp::slotShowBudgetContextMenu() { showContextMenu("budget_context_menu"); } void KMyMoneyApp::slotShowCurrencyContextMenu() { showContextMenu("currency_context_menu"); } void KMyMoneyApp::slotShowPriceContextMenu() { showContextMenu("price_context_menu"); } void KMyMoneyApp::slotShowOnlineJobContextMenu() { showContextMenu("onlinejob_context_menu"); } void KMyMoneyApp::slotPrintView() { d->m_myMoneyView->slotPrintView(); } void KMyMoneyApp::updateCaption(bool skipActions) { QString caption; caption = d->m_fileName.fileName(); if (caption.isEmpty() && d->m_myMoneyView && d->m_myMoneyView->fileOpen()) caption = i18n("Untitled"); // MyMoneyFile::instance()->dirty() throws an exception, if // there's no storage object available. In this case, we // assume that the storage object is not changed. Actually, // this can only happen if we are newly created early on. bool modified; try { modified = MyMoneyFile::instance()->dirty(); } catch (const MyMoneyException &) { modified = false; skipActions = true; } #ifdef KMM_DEBUG caption += QString(" (%1 x %2)").arg(width()).arg(height()); #endif setCaption(caption, modified); if (!skipActions) { d->m_myMoneyView->enableViewsIfFileOpen(); slotUpdateActions(); } } void KMyMoneyApp::slotUpdateActions() { MyMoneyFile* file = MyMoneyFile::instance(); const bool fileOpen = d->m_myMoneyView->fileOpen(); const bool modified = file->dirty(); const bool importRunning = (d->m_smtReader != 0); QWidget* w; KActionCollection *aC = actionCollection(); // ************* // Disabling actions to be disabled at this point // ************* { static const QVector disabledActions { Action::AccountStartReconciliation, Action::AccountFinishReconciliation, Action::AccountPostponeReconciliation, Action::AccountEdit, Action::AccountDelete, Action::AccountOpen, Action::AccountClose, Action::AccountReopen, Action::AccountTransactionReport, Action::AccountOnlineMap, Action::AccountOnlineUnmap, Action::AccountUpdate, Action::AccountUpdateAll, Action::AccountBalanceChart, Action::CategoryEdit, Action::CategoryDelete, Action::InstitutionEdit, Action::InstitutionDelete, Action::InvestmentEdit, Action::InvestmentNew, Action::InvestmentDelete, Action::InvestmentOnlinePrice, Action::InvestmentManualPrice, Action::ScheduleEdit, Action::ScheduleDelete, Action::ScheduleEnter, Action::ScheduleSkip, Action::PayeeDelete, Action::PayeeRename, Action::PayeeMerge, Action::TagDelete, Action::TagRename, Action::BudgetDelete, Action::BudgetRename, Action::BudgetChangeYear, Action::BudgetNew, Action::BudgetCopy, Action::BudgetForecast, Action::TransactionEdit, Action::TransactionEditSplits, Action::TransactionEnter, Action::TransactionCancel, Action::TransactionDelete, Action::TransactionMatch, Action::TransactionAccept, Action::TransactionDuplicate, Action::TransactionToggleReconciled, Action::TransactionToggleCleared, Action::TransactionGoToAccount, Action::TransactionGoToPayee, Action::TransactionAssignNumber, Action::TransactionCreateSchedule, Action::TransactionCombine, Action::TransactionSelectAll, Action::TransactionCopySplits, Action::ScheduleEdit, Action::ScheduleDelete, Action::ScheduleDuplicate, Action::ScheduleEnter, Action::ScheduleSkip, Action::CurrencyRename, Action::CurrencyDelete, Action::CurrencySetBase, Action::PriceEdit, Action::PriceDelete, Action::PriceUpdate }; foreach (const auto a, disabledActions) aC->action(s_Actions.value(a))->setEnabled(false); } // ************* // Disabling actions based on conditions // ************* { QString tooltip = i18n("Create a new transaction"); const QVector> actionStates { {qMakePair(Action::FileOpenDatabase, true)}, {qMakePair(Action::FileSaveAsDatabase, fileOpen)}, {qMakePair(Action::FilePersonalData, fileOpen)}, {qMakePair(Action::FileBackup, (fileOpen && !d->m_myMoneyView->isDatabase()))}, {qMakePair(Action::FileInformation, fileOpen)}, {qMakePair(Action::FileImportGNC, !importRunning)}, {qMakePair(Action::FileImportTemplate, fileOpen && !importRunning)}, {qMakePair(Action::FileExportTemplate, fileOpen && !importRunning)}, #ifdef KMM_DEBUG {qMakePair(Action::FileDump, fileOpen)}, #endif {qMakePair(Action::EditFindTransaction, fileOpen)}, {qMakePair(Action::ToolCurrencies, fileOpen)}, {qMakePair(Action::ToolPrices, fileOpen)}, {qMakePair(Action::ToolUpdatePrices, fileOpen)}, {qMakePair(Action::ToolConsistency, fileOpen)}, {qMakePair(Action::AccountNew, fileOpen)}, {qMakePair(Action::AccountCreditTransfer, onlineJobAdministration::instance()->canSendCreditTransfer())}, {qMakePair(Action::CategoryDelete, fileOpen)}, {qMakePair(Action::InstitutionNew, fileOpen)}, {qMakePair(Action::TransactionNew, (fileOpen && d->m_myMoneyView->canCreateTransactions(KMyMoneyRegister::SelectedTransactions(), tooltip)))}, {qMakePair(Action::ScheduleNew, fileOpen)}, {qMakePair(Action::CurrencyNew, fileOpen)}, {qMakePair(Action::PriceNew, fileOpen)}, }; foreach (const auto a, actionStates) aC->action(s_Actions.value(a.first))->setEnabled(a.second); } // ************* // Disabling standard actions based on conditions // ************* aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(modified && !d->m_myMoneyView->isDatabase()); aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::SaveAs)))->setEnabled(fileOpen); aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Close)))->setEnabled(fileOpen); aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Print)))->setEnabled(fileOpen && d->m_myMoneyView->canPrint()); aC->action(s_Actions[Action::TransactionMatch])->setText(i18nc("Button text for match transaction", "Match")); aC->action(s_Actions[Action::TransactionNew])->setToolTip(i18n("Create a new transaction")); w = factory()->container(s_Actions[Action::TransactionMoveMenu], this); if (w) w->setEnabled(false); w = factory()->container(s_Actions[Action::TransactionMarkMenu], this); if (w) w->setEnabled(false); w = factory()->container(s_Actions[Action::TransactionContextMarkMenu], this); if (w) w->setEnabled(false); // ************* // Enabling actions based on conditions // ************* // FIXME for now it's always on, but we should only allow it, if we // can select at least a single transaction aC->action(s_Actions[Action::TransactionSelectAll])->setEnabled(true); if (!d->m_selectedTransactions.isEmpty()) { // enable 'delete transaction' only if at least one of the // selected transactions does not reference a closed account bool enable = false; KMyMoneyRegister::SelectedTransactions::const_iterator it_t; for (it_t = d->m_selectedTransactions.constBegin(); (enable == false) && (it_t != d->m_selectedTransactions.constEnd()); ++it_t) { enable = !(*it_t).transaction().id().isEmpty() && !file->referencesClosedAccount((*it_t).transaction()); } aC->action(s_Actions[Action::TransactionDelete])->setEnabled(enable); if (!d->m_transactionEditor) { QString tooltip = i18n("Duplicate the current selected transactions"); aC->action(s_Actions[Action::TransactionDuplicate])->setEnabled(d->m_myMoneyView->canDuplicateTransactions(d->m_selectedTransactions, tooltip) && !d->m_selectedTransactions[0].transaction().id().isEmpty()); aC->action(s_Actions[Action::TransactionDuplicate])->setToolTip(tooltip); if (d->m_myMoneyView->canEditTransactions(d->m_selectedTransactions, tooltip)) { aC->action(s_Actions[Action::TransactionEdit])->setEnabled(true); // editing splits is allowed only if we have one transaction selected if (d->m_selectedTransactions.count() == 1) { aC->action(s_Actions[Action::TransactionEditSplits])->setEnabled(true); } if (d->m_selectedAccount.isAssetLiability() && d->m_selectedAccount.accountType() != eMyMoney::Account::Type::Investment) { aC->action(s_Actions[Action::TransactionCreateSchedule])->setEnabled(d->m_selectedTransactions.count() == 1); } } aC->action(s_Actions[Action::TransactionEdit])->setToolTip(tooltip); if (!d->m_selectedAccount.isClosed()) { w = factory()->container(s_Actions[Action::TransactionMoveMenu], this); if (w) w->setEnabled(true); } w = factory()->container(s_Actions[Action::TransactionMarkMenu], this); if (w) w->setEnabled(true); w = factory()->container(s_Actions[Action::TransactionContextMarkMenu], this); if (w) w->setEnabled(true); // Allow marking the transaction if at least one is selected aC->action(s_Actions[Action::TransactionToggleCleared])->setEnabled(true); aC->action(s_Actions[Action::TransactionReconciled])->setEnabled(true); aC->action(s_Actions[Action::TransactionNotReconciled])->setEnabled(true); aC->action(s_Actions[Action::TransactionToggleReconciled])->setEnabled(true); if (!d->m_accountGoto.isEmpty()) aC->action(s_Actions[Action::TransactionGoToAccount])->setEnabled(true); if (!d->m_payeeGoto.isEmpty()) aC->action(s_Actions[Action::TransactionGoToPayee])->setEnabled(true); // Matching is enabled as soon as one regular and one imported transaction is selected int matchedCount = 0; int importedCount = 0; KMyMoneyRegister::SelectedTransactions::const_iterator it; for (it = d->m_selectedTransactions.constBegin(); it != d->m_selectedTransactions.constEnd(); ++it) { if ((*it).transaction().isImported()) ++importedCount; if ((*it).split().isMatched()) ++matchedCount; } if (d->m_selectedTransactions.count() == 2 /* && aC->action(s_Actions[Action::TransactionEdit])->isEnabled() */) { aC->action(s_Actions[Action::TransactionMatch])->setEnabled(true); } if (importedCount != 0 || matchedCount != 0) aC->action(s_Actions[Action::TransactionAccept])->setEnabled(true); if (matchedCount != 0) { aC->action(s_Actions[Action::TransactionMatch])->setEnabled(true); aC->action(s_Actions[Action::TransactionMatch])->setText(i18nc("Button text for unmatch transaction", "Unmatch")); aC->action(s_Actions[Action::TransactionMatch])->setIcon(QIcon("process-stop")); } if (d->m_selectedTransactions.count() > 1) { aC->action(s_Actions[Action::TransactionCombine])->setEnabled(true); } if (d->m_selectedTransactions.count() >= 2) { int singleSplitTransactions = 0; int multipleSplitTransactions = 0; foreach (const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) { switch (st.transaction().splitCount()) { case 0: break; case 1: singleSplitTransactions++; break; default: multipleSplitTransactions++; break; } } if (singleSplitTransactions > 0 && multipleSplitTransactions == 1) { aC->action(s_Actions[Action::TransactionCopySplits])->setEnabled(true); } } if (d->m_selectedTransactions.count() >= 2) { int singleSplitTransactions = 0; int multipleSplitTransactions = 0; foreach(const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) { switch(st.transaction().splitCount()) { case 0: break; case 1: singleSplitTransactions++; break; default: multipleSplitTransactions++; break; } } if(singleSplitTransactions > 0 && multipleSplitTransactions == 1) { aC->action(s_Actions[Action::TransactionCopySplits])->setEnabled(true); } } } else { aC->action(s_Actions[Action::TransactionAssignNumber])->setEnabled(d->m_transactionEditor->canAssignNumber()); aC->action(s_Actions[Action::TransactionNew])->setEnabled(false); aC->action(s_Actions[Action::TransactionDelete])->setEnabled(false); QString reason; aC->action(s_Actions[Action::TransactionEnter])->setEnabled(d->m_transactionEditor->isComplete(reason)); //FIXME: Port to KDE4 // the next line somehow worked in KDE3 but does not have // any influence under KDE4 /// Works for me when 'reason' is set. Allan aC->action(s_Actions[Action::TransactionEnter])->setToolTip(reason); aC->action(s_Actions[Action::TransactionCancel])->setEnabled(true); } } QList accList; file->accountList(accList); QList::const_iterator it_a; QMap::const_iterator it_p = d->m_onlinePlugins.constEnd(); for (it_a = accList.constBegin(); (it_p == d->m_onlinePlugins.constEnd()) && (it_a != accList.constEnd()); ++it_a) { if (!(*it_a).onlineBankingSettings().value("provider").isEmpty()) { // check if provider is available it_p = d->m_onlinePlugins.constFind((*it_a).onlineBankingSettings().value("provider")); if (it_p != d->m_onlinePlugins.constEnd()) { QStringList protocols; (*it_p)->protocols(protocols); if (protocols.count() > 0) { aC->action(s_Actions[Action::AccountUpdateAll])->setEnabled(true); aC->action(s_Actions[Action::AccountUpdateMenu])->setEnabled(true); } } } } QBitArray skip((int)eStorage::Reference::Count); if (!d->m_selectedAccount.id().isEmpty()) { if (!file->isStandardAccount(d->m_selectedAccount.id())) { switch (d->m_selectedAccount.accountGroup()) { case eMyMoney::Account::Type::Asset: case eMyMoney::Account::Type::Liability: case eMyMoney::Account::Type::Equity: aC->action(s_Actions[Action::AccountTransactionReport])->setEnabled(true); aC->action(s_Actions[Action::AccountEdit])->setEnabled(true); aC->action(s_Actions[Action::AccountDelete])->setEnabled(!file->isReferenced(d->m_selectedAccount)); aC->action(s_Actions[Action::AccountOpen])->setEnabled(true); if (d->m_selectedAccount.accountGroup() != eMyMoney::Account::Type::Equity) { if (d->m_reconciliationAccount.id().isEmpty()) { aC->action(s_Actions[Action::AccountStartReconciliation])->setEnabled(true); aC->action(s_Actions[Action::AccountStartReconciliation])->setToolTip(i18n("Reconcile")); } else { QString tip; tip = i18n("Reconcile - disabled because you are currently reconciling %1", d->m_reconciliationAccount.name()); aC->action(s_Actions[Action::AccountStartReconciliation])->setToolTip(tip); if (!d->m_transactionEditor) { aC->action(s_Actions[Action::AccountFinishReconciliation])->setEnabled(d->m_selectedAccount.id() == d->m_reconciliationAccount.id()); aC->action(s_Actions[Action::AccountPostponeReconciliation])->setEnabled(d->m_selectedAccount.id() == d->m_reconciliationAccount.id()); } } } if (d->m_selectedAccount.accountType() == eMyMoney::Account::Type::Investment) aC->action(s_Actions[Action::InvestmentNew])->setEnabled(true); if (d->m_selectedAccount.isClosed()) aC->action(s_Actions[Action::AccountReopen])->setEnabled(true); else enableCloseAccountAction(d->m_selectedAccount); if (!d->m_selectedAccount.onlineBankingSettings().value("provider").isEmpty()) { aC->action(s_Actions[Action::AccountOnlineUnmap])->setEnabled(true); // check if provider is available QMap::const_iterator it_p; it_p = d->m_onlinePlugins.constFind(d->m_selectedAccount.onlineBankingSettings().value("provider")); if (it_p != d->m_onlinePlugins.constEnd()) { QStringList protocols; (*it_p)->protocols(protocols); if (protocols.count() > 0) { aC->action(s_Actions[Action::AccountUpdate])->setEnabled(true); aC->action(s_Actions[Action::AccountUpdateMenu])->setEnabled(true); } } } else { aC->action(s_Actions[Action::AccountOnlineMap])->setEnabled(d->m_onlinePlugins.count() > 0); } aC->action(s_Actions[Action::AccountBalanceChart])->setEnabled(true); break; case eMyMoney::Account::Type::Income : case eMyMoney::Account::Type::Expense : aC->action(s_Actions[Action::CategoryEdit])->setEnabled(true); // enable delete action, if category/account itself is not referenced // by any object except accounts, because we want to allow // deleting of sub-categories. Also, we allow transactions, schedules and budgets // to be present because we can re-assign them during the delete process skip.fill(false); skip.setBit((int)eStorage::Reference::Transaction); skip.setBit((int)eStorage::Reference::Account); skip.setBit((int)eStorage::Reference::Schedule); skip.setBit((int)eStorage::Reference::Budget); aC->action(s_Actions[Action::CategoryDelete])->setEnabled(!file->isReferenced(d->m_selectedAccount, skip)); aC->action(s_Actions[Action::AccountOpen])->setEnabled(true); break; default: break; } } } if (!d->m_selectedInstitution.id().isEmpty()) { aC->action(s_Actions[Action::InstitutionEdit])->setEnabled(true); aC->action(s_Actions[Action::InstitutionDelete])->setEnabled(!file->isReferenced(d->m_selectedInstitution)); } if (!d->m_selectedInvestment.id().isEmpty()) { aC->action(s_Actions[Action::InvestmentEdit])->setEnabled(true); aC->action(s_Actions[Action::InvestmentDelete])->setEnabled(!file->isReferenced(d->m_selectedInvestment)); aC->action(s_Actions[Action::InvestmentManualPrice])->setEnabled(true); try { MyMoneySecurity security = MyMoneyFile::instance()->security(d->m_selectedInvestment.currencyId()); if (!security.value("kmm-online-source").isEmpty()) aC->action(s_Actions[Action::InvestmentOnlinePrice])->setEnabled(true); } catch (const MyMoneyException &e) { qDebug("Error retrieving security for investment %s: %s", qPrintable(d->m_selectedInvestment.name()), qPrintable(e.what())); } if (d->m_selectedInvestment.isClosed()) aC->action(s_Actions[Action::AccountReopen])->setEnabled(true); else enableCloseAccountAction(d->m_selectedInvestment); } if (!d->m_selectedSchedule.id().isEmpty()) { aC->action(s_Actions[Action::ScheduleEdit])->setEnabled(true); aC->action(s_Actions[Action::ScheduleDuplicate])->setEnabled(true); aC->action(s_Actions[Action::ScheduleDelete])->setEnabled(!file->isReferenced(d->m_selectedSchedule)); if (!d->m_selectedSchedule.isFinished()) { aC->action(s_Actions[Action::ScheduleEnter])->setEnabled(true); // a schedule with a single occurrence cannot be skipped if (d->m_selectedSchedule.occurrence() != eMyMoney::Schedule::Occurrence::Once) { aC->action(s_Actions[Action::ScheduleSkip])->setEnabled(true); } } } if (d->m_selectedPayees.count() >= 1) { aC->action(s_Actions[Action::PayeeRename])->setEnabled(d->m_selectedPayees.count() == 1); aC->action(s_Actions[Action::PayeeMerge])->setEnabled(d->m_selectedPayees.count() > 1); aC->action(s_Actions[Action::PayeeDelete])->setEnabled(true); } if (d->m_selectedTags.count() >= 1) { aC->action(s_Actions[Action::TagRename])->setEnabled(d->m_selectedTags.count() == 1); aC->action(s_Actions[Action::TagDelete])->setEnabled(true); } aC->action(s_Actions[Action::BudgetNew])->setEnabled(true); if (d->m_selectedBudgets.count() >= 1) { aC->action(s_Actions[Action::BudgetDelete])->setEnabled(true); if (d->m_selectedBudgets.count() == 1) { aC->action(s_Actions[Action::BudgetChangeYear])->setEnabled(true); aC->action(s_Actions[Action::BudgetCopy])->setEnabled(true); aC->action(s_Actions[Action::BudgetRename])->setEnabled(true); aC->action(s_Actions[Action::BudgetForecast])->setEnabled(true); } } if (!d->m_selectedCurrency.id().isEmpty()) { aC->action(s_Actions[Action::CurrencyRename])->setEnabled(true); // no need to check each transaction. accounts are enough in this case skip.fill(false); skip.setBit((int)eStorage::Reference::Transaction); aC->action(s_Actions[Action::CurrencyDelete])->setEnabled(!file->isReferenced(d->m_selectedCurrency, skip)); if (d->m_selectedCurrency.id() != file->baseCurrency().id()) aC->action(s_Actions[Action::CurrencySetBase])->setEnabled(true); } if (!d->m_selectedPrice.from().isEmpty() && d->m_selectedPrice.source() != QLatin1String("KMyMoney")) { aC->action(s_Actions[Action::PriceEdit])->setEnabled(true); aC->action(s_Actions[Action::PriceDelete])->setEnabled(true); //enable online update if it is a currency MyMoneySecurity security = MyMoneyFile::instance()->security(d->m_selectedPrice.from()); aC->action(s_Actions[Action::PriceUpdate])->setEnabled(security.isCurrency()); } } void KMyMoneyApp::slotResetSelections() { slotSelectAccount(MyMoneyAccount()); slotSelectInstitution(MyMoneyInstitution()); slotSelectInvestment(MyMoneyAccount()); slotSelectSchedule(); slotSelectCurrency(); slotSelectPrice(); slotSelectPayees(QList()); slotSelectTags(QList()); slotSelectBudget(QList()); slotSelectTransactions(KMyMoneyRegister::SelectedTransactions()); slotUpdateActions(); } void KMyMoneyApp::slotSelectCurrency() { slotSelectCurrency(MyMoneySecurity()); } void KMyMoneyApp::slotSelectCurrency(const MyMoneySecurity& currency) { d->m_selectedCurrency = currency; slotUpdateActions(); emit currencySelected(d->m_selectedCurrency); } void KMyMoneyApp::slotSelectPrice() { slotSelectPrice(MyMoneyPrice()); } void KMyMoneyApp::slotSelectPrice(const MyMoneyPrice& price) { d->m_selectedPrice = price; slotUpdateActions(); emit priceSelected(d->m_selectedPrice); } void KMyMoneyApp::slotSelectBudget(const QList& list) { d->m_selectedBudgets = list; slotUpdateActions(); emit budgetSelected(d->m_selectedBudgets); } void KMyMoneyApp::slotSelectPayees(const QList& list) { d->m_selectedPayees = list; slotUpdateActions(); emit payeesSelected(d->m_selectedPayees); } void KMyMoneyApp::slotSelectTags(const QList& list) { d->m_selectedTags = list; slotUpdateActions(); emit tagsSelected(d->m_selectedTags); } void KMyMoneyApp::slotSelectTransactions(const KMyMoneyRegister::SelectedTransactions& list) { // list can either contain a list of transactions or a single selected scheduled transaction // in the latter case, the transaction id is actually the one of the schedule. In order // to differentiate between the two, we just ask for the schedule. If we don't find one - because // we passed the id of a real transaction - then we know that fact. We use the schedule here, // because the list of schedules is kept in a cache by MyMoneyFile. This way, we save some trips // to the backend which we would have to do if we check for the transaction. d->m_selectedTransactions.clear(); d->m_selectedSchedule = MyMoneySchedule(); d->m_accountGoto.clear(); d->m_payeeGoto.clear(); if (!list.isEmpty() && !list.first().isScheduled()) { d->m_selectedTransactions = list; if (list.count() == 1) { const MyMoneySplit& sp = d->m_selectedTransactions[0].split(); if (!sp.payeeId().isEmpty()) { try { MyMoneyPayee payee = MyMoneyFile::instance()->payee(sp.payeeId()); if (!payee.name().isEmpty()) { d->m_payeeGoto = payee.id(); QString name = payee.name(); name.replace(QRegExp("&(?!&)"), "&&"); actionCollection()->action(s_Actions[Action::TransactionGoToPayee])->setText(i18n("Go to '%1'", name)); } } catch (const MyMoneyException &) { } } try { QList::const_iterator it_s; const MyMoneyTransaction& t = d->m_selectedTransactions[0].transaction(); // search the first non-income/non-expense accunt and use it for the 'goto account' const MyMoneySplit& sp = d->m_selectedTransactions[0].split(); for (it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) { if ((*it_s).id() != sp.id()) { MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId()); if (!acc.isIncomeExpense()) { // for stock accounts we show the portfolio account if (acc.isInvest()) { acc = MyMoneyFile::instance()->account(acc.parentAccountId()); } d->m_accountGoto = acc.id(); QString name = acc.name(); name.replace(QRegExp("&(?!&)"), "&&"); actionCollection()->action(s_Actions[Action::TransactionGoToAccount])->setText(i18n("Go to '%1'", name)); break; } } } } catch (const MyMoneyException &) { } } slotUpdateActions(); emit transactionsSelected(d->m_selectedTransactions); } else if (!list.isEmpty()) { slotSelectSchedule(MyMoneyFile::instance()->schedule(list.first().scheduleId())); } else { slotUpdateActions(); } // make sure, we show some neutral menu entry if we don't have an object if (d->m_payeeGoto.isEmpty()) actionCollection()->action(s_Actions[Action::TransactionGoToPayee])->setText(i18n("Go to payee")); if (d->m_accountGoto.isEmpty()) actionCollection()->action(s_Actions[Action::TransactionGoToAccount])->setText(i18n("Go to account")); } void KMyMoneyApp::slotSelectInstitution(const MyMoneyObject& institution) { if (typeid(institution) != typeid(MyMoneyInstitution)) return; d->m_selectedInstitution = dynamic_cast(institution); // qDebug("slotSelectInstitution('%s')", d->m_selectedInstitution.name().data()); slotUpdateActions(); emit institutionSelected(d->m_selectedInstitution); } void KMyMoneyApp::slotSelectAccount(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyAccount)) return; d->m_selectedAccount = MyMoneyAccount(); const MyMoneyAccount& acc = dynamic_cast(obj); if (!acc.isInvest()) d->m_selectedAccount = acc; // qDebug("slotSelectAccount('%s')", d->m_selectedAccount.name().data()); slotUpdateActions(); emit accountSelected(d->m_selectedAccount); } void KMyMoneyApp::slotSelectInvestment(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyAccount)) return; // qDebug("slotSelectInvestment('%s')", account.name().data()); d->m_selectedInvestment = MyMoneyAccount(); const MyMoneyAccount& acc = dynamic_cast(obj); if (acc.isInvest()) d->m_selectedInvestment = acc; slotUpdateActions(); emit investmentSelected(d->m_selectedInvestment); } void KMyMoneyApp::slotSelectSchedule() { slotSelectSchedule(MyMoneySchedule()); } void KMyMoneyApp::slotSelectSchedule(const MyMoneySchedule& schedule) { // qDebug("slotSelectSchedule('%s')", schedule.name().data()); d->m_selectedSchedule = schedule; slotUpdateActions(); emit scheduleSelected(d->m_selectedSchedule); } void KMyMoneyApp::slotDataChanged() { // As this method is called every time the MyMoneyFile instance // notifies a modification, it's the perfect place to start the timer if needed if (d->m_autoSaveEnabled && !d->m_autoSaveTimer->isActive()) { d->m_autoSaveTimer->setSingleShot(true); d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000); //miliseconds } updateCaption(); } void KMyMoneyApp::slotCurrencyDialog() { QPointer dlg = new KCurrencyEditDlg(this); connect(dlg, SIGNAL(selectObject(MyMoneySecurity)), this, SLOT(slotSelectCurrency(MyMoneySecurity))); connect(dlg, SIGNAL(openContextMenu(MyMoneySecurity)), this, SLOT(slotShowCurrencyContextMenu())); connect(this, SIGNAL(currencyRename()), dlg, SLOT(slotStartRename())); connect(dlg, SIGNAL(updateCurrency(QString,QString,QString)), this, SLOT(slotCurrencyUpdate(QString,QString,QString))); connect(this, SIGNAL(currencyCreated(QString)), dlg, SLOT(slotSelectCurrency(QString))); connect(dlg, SIGNAL(selectBaseCurrency(MyMoneySecurity)), this, SLOT(slotCurrencySetBase())); dlg->exec(); delete dlg; slotSelectCurrency(MyMoneySecurity()); } void KMyMoneyApp::slotPriceDialog() { QPointer dlg = new KMyMoneyPriceDlg(this); connect(dlg, SIGNAL(selectObject(MyMoneyPrice)), this, SLOT(slotSelectPrice(MyMoneyPrice))); connect(dlg, SIGNAL(openContextMenu(MyMoneyPrice)), this, SLOT(slotShowPriceContextMenu())); connect(this, SIGNAL(priceNew()), dlg, SLOT(slotNewPrice())); connect(this, SIGNAL(priceEdit()), dlg, SLOT(slotEditPrice())); connect(this, SIGNAL(priceDelete()), dlg, SLOT(slotDeletePrice())); connect(this, SIGNAL(priceOnlineUpdate()), dlg, SLOT(slotOnlinePriceUpdate())); dlg->exec(); } void KMyMoneyApp::slotFileConsistencyCheck() { d->consistencyCheck(true); updateCaption(); } void KMyMoneyApp::Private::consistencyCheck(bool alwaysDisplayResult) { KMSTATUS(i18n("Running consistency check...")); MyMoneyFileTransaction ft; try { m_consistencyCheckResult = MyMoneyFile::instance()->consistencyCheck(); ft.commit(); } catch (const MyMoneyException &e) { m_consistencyCheckResult.append(i18n("Consistency check failed: %1", e.what())); // always display the result if the check failed alwaysDisplayResult = true; } // in case the consistency check was OK, we get a single line as result // in all errneous cases, we get more than one line and force the // display of them. if (alwaysDisplayResult || m_consistencyCheckResult.size() > 1) { QString msg = i18n("The consistency check has found no issues in your data. Details are presented below."); if (m_consistencyCheckResult.size() > 1) msg = i18n("The consistency check has found some issues in your data. Details are presented below. Those issues that could not be corrected automatically need to be solved by the user."); // install a context menu for the list after the dialog is displayed QTimer::singleShot(500, q, SLOT(slotInstallConsistencyCheckContextMenu())); KMessageBox::informationList(0, msg, m_consistencyCheckResult, i18n("Consistency check result")); } // this data is no longer needed m_consistencyCheckResult.clear(); } void KMyMoneyApp::Private::copyConsistencyCheckResults() { QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(m_consistencyCheckResult.join(QLatin1String("\n"))); } void KMyMoneyApp::Private::saveConsistencyCheckResults() { QUrl fileUrl = QFileDialog::getSaveFileUrl(q); if (!fileUrl.isEmpty()) { QFile file(fileUrl.toLocalFile()); if (file.open(QFile::WriteOnly | QFile::Append | QFile::Text)) { QTextStream out(&file); out << m_consistencyCheckResult.join(QLatin1String("\n")); file.close(); } } } void KMyMoneyApp::Private::setThemedCSS() { const QStringList CSSnames {QStringLiteral("kmymoney.css"), QStringLiteral("welcome.css")}; const QString rcDir("/html/"); const QStringList defaultCSSDirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, rcDir, QStandardPaths::LocateDirectory); // scan the list of directories to find the ones that really // contains all files we look for QString defaultCSSDir; foreach (const auto dir, defaultCSSDirs) { defaultCSSDir = dir; foreach (const auto CSSname, CSSnames) { QFileInfo fileInfo(defaultCSSDir + CSSname); if (!fileInfo.exists()) { defaultCSSDir.clear(); break; } } if (!defaultCSSDir.isEmpty()) { break; } } // make sure we have the local directory where the themed version is stored const QString themedCSSDir = QStandardPaths::standardLocations(QStandardPaths::AppConfigLocation).first() + rcDir; QDir().mkpath(themedCSSDir); foreach (const auto CSSname, CSSnames) { const QString defaultCSSFilename = defaultCSSDir + CSSname; QFileInfo fileInfo(defaultCSSFilename); if (fileInfo.exists()) { const QString themedCSSFilename = themedCSSDir + CSSname; QFile::remove(themedCSSFilename); if (QFile::copy(defaultCSSFilename, themedCSSFilename)) { QFile cssFile (themedCSSFilename); if (cssFile.open(QIODevice::ReadWrite)) { QTextStream cssStream(&cssFile); auto cssText = cssStream.readAll(); cssText.replace(QLatin1String("./"), defaultCSSDir, Qt::CaseSensitive); cssText.replace(QLatin1String("WindowText"), KMyMoneyGlobalSettings::schemeColor(SchemeColor::WindowText).name(), Qt::CaseSensitive); cssText.replace(QLatin1String("Window"), KMyMoneyGlobalSettings::schemeColor(SchemeColor::WindowBackground).name(), Qt::CaseSensitive); cssText.replace(QLatin1String("HighlightText"), KMyMoneyGlobalSettings::schemeColor(SchemeColor::ListHighlightText).name(), Qt::CaseSensitive); cssText.replace(QLatin1String("Highlight"), KMyMoneyGlobalSettings::schemeColor(SchemeColor::ListHighlight).name(), Qt::CaseSensitive); cssText.replace(QLatin1String("black"), KMyMoneyGlobalSettings::schemeColor(SchemeColor::ListGrid).name(), Qt::CaseSensitive); cssStream.seek(0); cssStream << cssText; cssFile.close(); } } } } } void KMyMoneyApp::slotCheckSchedules() { if (KMyMoneyGlobalSettings::checkSchedule() == true) { KMSTATUS(i18n("Checking for overdue scheduled transactions...")); MyMoneyFile *file = MyMoneyFile::instance(); QDate checkDate = QDate::currentDate().addDays(KMyMoneyGlobalSettings::checkSchedulePreview()); QList scheduleList = file->scheduleList(); QList::Iterator it; eDialogs::ScheduleResultCode rc = eDialogs::ScheduleResultCode::Enter; for (it = scheduleList.begin(); (it != scheduleList.end()) && (rc != eDialogs::ScheduleResultCode::Cancel); ++it) { // Get the copy in the file because it might be modified by commitTransaction MyMoneySchedule schedule = file->schedule((*it).id()); if (schedule.autoEnter()) { try { while (!schedule.isFinished() && (schedule.adjustedNextDueDate() <= checkDate) && rc != eDialogs::ScheduleResultCode::Ignore && rc != eDialogs::ScheduleResultCode::Cancel) { rc = enterSchedule(schedule, true, true); schedule = file->schedule((*it).id()); // get a copy of the modified schedule } } catch (const MyMoneyException &) { } } if (rc == eDialogs::ScheduleResultCode::Ignore) { // if the current schedule was ignored then we must make sure that the user can still enter the next scheduled transaction rc = eDialogs::ScheduleResultCode::Enter; } } updateCaption(); } } void KMyMoneyApp::writeLastUsedDir(const QString& directory) { //get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { KConfigGroup grp = kconfig->group("General Options"); //write path entry, no error handling since its void. grp.writeEntry("LastUsedDirectory", directory); } } void KMyMoneyApp::writeLastUsedFile(const QString& fileName) { //get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { KConfigGroup grp = d->m_config->group("General Options"); // write path entry, no error handling since its void. // use a standard string, as fileName could contain a protocol // e.g. file:/home/thb/.... grp.writeEntry("LastUsedFile", fileName); } } QString KMyMoneyApp::readLastUsedDir() const { QString str; //get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { KConfigGroup grp = d->m_config->group("General Options"); //read path entry. Second parameter is the default if the setting is not found, which will be the default document path. str = grp.readEntry("LastUsedDirectory", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); // if the path stored is empty, we use the default nevertheless if (str.isEmpty()) str = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); } return str; } QString KMyMoneyApp::readLastUsedFile() const { QString str; // get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { KConfigGroup grp = d->m_config->group("General Options"); // read filename entry. str = grp.readEntry("LastUsedFile", ""); } return str; } QString KMyMoneyApp::filename() const { return d->m_fileName.url(); } WebConnect* KMyMoneyApp::webConnect() const { return d->m_webConnect; } QList KMyMoneyApp::instanceList() const { QList list; #ifdef KMM_DBUS QDBusReply reply = QDBusConnection::sessionBus().interface()->registeredServiceNames(); if (reply.isValid()) { QStringList apps = reply.value(); QStringList::ConstIterator it; // build a list of service names of all running kmymoney applications without this one for (it = apps.constBegin(); it != apps.constEnd(); ++it) { // please change this method of creating a list of 'all the other kmymoney instances that are running on the system' // since assuming that D-Bus creates service names with org.kde.kmymoney-PID is an observation I don't think that it's documented somwhere if ((*it).indexOf("org.kde.kmymoney-") == 0) { uint thisProcPid = platformTools::processId(); if ((*it).indexOf(QString("org.kde.kmymoney-%1").arg(thisProcPid)) != 0) list += (*it); } } } else { qDebug("D-Bus returned the following error while obtaining instances: %s", qPrintable(reply.error().message())); } #endif return list; } void KMyMoneyApp::slotEquityPriceUpdate() { QPointer dlg = new KEquityPriceUpdateDlg(this); if (dlg->exec() == QDialog::Accepted && dlg != 0) dlg->storePrices(); delete dlg; } void KMyMoneyApp::webConnect(const QString& sourceUrl, const QByteArray& asn_id) { // // Web connect attempts to go through the known importers and see if the file // can be importing using that method. If so, it will import it using that // plugin // Q_UNUSED(asn_id) d->m_importUrlsQueue.enqueue(sourceUrl); // only start processing if this is the only import so far if (d->m_importUrlsQueue.count() == 1) { while (!d->m_importUrlsQueue.isEmpty()) { // get the value of the next item from the queue // but leave it on the queue for now QString url = d->m_importUrlsQueue.head(); // Bring this window to the forefront. This method was suggested by // Lubos Lunak of the KDE core development team. // TODO: port KF5 (WebConnect) //KStartupInfo::setNewStartupId(this, asn_id); // Make sure we have an open file if (! d->m_myMoneyView->fileOpen() && KMessageBox::warningContinueCancel(kmymoney, i18n("You must first select a KMyMoney file before you can import a statement.")) == KMessageBox::Continue) kmymoney->slotFileOpen(); // only continue if the user really did open a file. if (d->m_myMoneyView->fileOpen()) { KMSTATUS(i18n("Importing a statement via Web Connect")); // remove the statement files d->unlinkStatementXML(); QMap::const_iterator it_plugin = d->m_importerPlugins.constBegin(); while (it_plugin != d->m_importerPlugins.constEnd()) { if ((*it_plugin)->isMyFormat(url)) { QList statements; if (!(*it_plugin)->import(url)) { KMessageBox::error(this, i18n("Unable to import %1 using %2 plugin. The plugin returned the following error: %3", url, (*it_plugin)->formatName(), (*it_plugin)->lastError()), i18n("Importing error")); } break; } ++it_plugin; } // If we did not find a match, try importing it as a KMM statement file, // which is really just for testing. the statement file is not exposed // to users. if (it_plugin == d->m_importerPlugins.constEnd()) if (MyMoneyStatement::isStatementFile(url)) slotStatementImport(url); } // remove the current processed item from the queue d->m_importUrlsQueue.dequeue(); } } } void KMyMoneyApp::slotEnableMessages() { KMessageBox::enableAllMessages(); KMessageBox::information(this, i18n("All messages have been enabled."), i18n("All messages")); } void KMyMoneyApp::createInterfaces() { // Sets up the plugin interface KMyMoneyPlugin::pluginInterfaces().importInterface = new KMyMoneyPlugin::KMMImportInterface(this, this); KMyMoneyPlugin::pluginInterfaces().statementInterface = new KMyMoneyPlugin::KMMStatementInterface(this, this); KMyMoneyPlugin::pluginInterfaces().viewInterface = new KMyMoneyPlugin::KMMViewInterface(this, d->m_myMoneyView, this); // setup the calendar interface for schedules MyMoneySchedule::setProcessingCalendar(this); } void KMyMoneyApp::loadPlugins() { Q_ASSERT(!d->m_pluginLoader); d->m_pluginLoader = new KMyMoneyPlugin::PluginLoader(this); //! @todo Junior Job: Improve the config read system KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group{ config->group("Plugins") }; const auto plugins = KPluginLoader::findPlugins("kmymoney"); d->m_pluginLoader->addPluginInfo(plugins); for (const KPluginMetaData & pluginData : plugins) { // Only load plugins which are enabled and have the right serviceType. Other serviceTypes are loaded on demand. if (isPluginEnabled(pluginData, group)) slotPluginLoad(pluginData); } connect(d->m_pluginLoader, &KMyMoneyPlugin::PluginLoader::pluginEnabled, this, &KMyMoneyApp::slotPluginLoad); connect(d->m_pluginLoader, &KMyMoneyPlugin::PluginLoader::pluginDisabled, this, &KMyMoneyApp::slotPluginUnload); } void KMyMoneyApp::unloadPlugins() { Q_ASSERT(d->m_pluginLoader); delete d->m_pluginLoader; } inline bool KMyMoneyApp::isPluginEnabled(const KPluginMetaData& metaData, const KConfigGroup& configGroup) { //! @fixme: there is a function in KMyMoneyPlugin::PluginLoader which has to have the same content if (metaData.serviceTypes().contains("KMyMoney/Plugin")) { const QString keyName{metaData.name() + "Enabled"}; if (configGroup.hasKey(keyName)) return configGroup.readEntry(keyName, true); return metaData.isEnabledByDefault(); } return false; } void KMyMoneyApp::slotPluginLoad(const KPluginMetaData& metaData) { std::unique_ptr loader{new QPluginLoader{metaData.fileName()}}; QObject* plugin = loader->instance(); if (!plugin) { qWarning("Could not load plugin '%s', error: %s", qPrintable(metaData.fileName()), qPrintable(loader->errorString())); return; } if ( d->m_plugins.contains(metaData.fileName()) ) { /** @fixme Handle a reload e.g. objectNames are equal but the object are different (plugin != d->m_plugins[plugin->objectName()]) * Also it could be useful to drop the dependence on objectName() */ /* Note: there is nothing to delete here because if the plugin was loaded already, plugin points to the same object as the previously loaded one. */ return; } KMyMoneyPlugin::Plugin* kmmPlugin = qobject_cast(plugin); if (!kmmPlugin) { qWarning("Could not load plugin '%s'.", qPrintable(metaData.fileName())); return; } // check for online plugin KMyMoneyPlugin::OnlinePlugin* op = dynamic_cast(plugin); // check for extended online plugin KMyMoneyPlugin::OnlinePluginExtended* ope = dynamic_cast(plugin); // check for importer plugin KMyMoneyPlugin::ImporterPlugin* ip = dynamic_cast(plugin); // Tell the plugin it is about to get plugged kmmPlugin->plug(); // plug the plugin guiFactory()->addClient(kmmPlugin); d->m_plugins[metaData.fileName()] = kmmPlugin; if (op) d->m_onlinePlugins[plugin->objectName()] = op; if (ope) onlineJobAdministration::instance()->addPlugin(plugin->objectName(), ope); if (ip) d->m_importerPlugins[plugin->objectName()] = ip; slotUpdateActions(); } void KMyMoneyApp::slotPluginUnload(const KPluginMetaData& metaData) { KMyMoneyPlugin::Plugin* plugin = d->m_plugins[metaData.fileName()]; // Remove and test if the plugin was actually loaded if (!d->m_plugins.remove(metaData.fileName()) || plugin == nullptr) return; // check for online plugin KMyMoneyPlugin::OnlinePlugin* op = dynamic_cast(plugin); // check for importer plugin KMyMoneyPlugin::ImporterPlugin* ip = dynamic_cast(plugin); // unplug the plugin guiFactory()->removeClient(plugin); if (op) d->m_onlinePlugins.remove(plugin->objectName()); if (ip) d->m_importerPlugins.remove(plugin->objectName()); plugin->unplug(); slotUpdateActions(); } void KMyMoneyApp::slotAutoSave() { if (!d->m_inAutoSaving) { // store the focus widget so we can restore it after save QPointer focusWidget = qApp->focusWidget(); d->m_inAutoSaving = true; KMSTATUS(i18n("Auto saving...")); //calls slotFileSave if needed, and restart the timer //it the file is not saved, reinitializes the countdown. if (d->m_myMoneyView->dirty() && d->m_autoSaveEnabled) { if (!slotFileSave() && d->m_autoSavePeriod > 0) { d->m_autoSaveTimer->setSingleShot(true); d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000); } } d->m_inAutoSaving = false; if (focusWidget && focusWidget != qApp->focusWidget()) { // we have a valid focus widget so restore it focusWidget->setFocus(); } } } void KMyMoneyApp::slotDateChanged() { QDateTime dt = QDateTime::currentDateTime(); QDateTime nextDay(QDate(dt.date().addDays(1)), QTime(0, 0, 0)); // +1 is to make sure that we're already in the next day when the // signal is sent (this way we also avoid setting the timer to 0) QTimer::singleShot((dt.secsTo(nextDay) + 1)*1000, this, SLOT(slotDateChanged())); d->m_myMoneyView->slotRefreshViews(); } MyMoneyAccount KMyMoneyApp::account(const QString& key, const QString& value) const { QList list; QList::const_iterator it_a; MyMoneyFile::instance()->accountList(list); QString accId; for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { // search in the account's kvp container const QString& accountKvpValue = (*it_a).value(key); // search in the account's online settings kvp container const QString& onlineSettingsKvpValue = (*it_a).onlineBankingSettings().value(key); if (accountKvpValue.contains(value) || onlineSettingsKvpValue.contains(value)) { if(accId.isEmpty()) { accId = (*it_a).id(); } } if (accountKvpValue == value || onlineSettingsKvpValue == value) { accId = (*it_a).id(); break; } } // return the account found or an empty element return MyMoneyFile::instance()->account(accId); } void KMyMoneyApp::setAccountOnlineParameters(const MyMoneyAccount& _acc, const MyMoneyKeyValueContainer& kvps) { MyMoneyFileTransaction ft; try { MyMoneyAccount acc = MyMoneyFile::instance()->account(_acc.id()); acc.setOnlineBankingSettings(kvps); MyMoneyFile::instance()->modifyAccount(acc); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to setup online parameters for account '%1'", _acc.name()), e.what()); } } void KMyMoneyApp::slotAccountUnmapOnline() { // no account selected if (d->m_selectedAccount.id().isEmpty()) return; // not a mapped account if (d->m_selectedAccount.onlineBankingSettings().value("provider").isEmpty()) return; if (KMessageBox::warningYesNo(this, QString("%1").arg(i18n("Do you really want to remove the mapping of account %1 to an online account? Depending on the details of the online banking method used, this action cannot be reverted.", d->m_selectedAccount.name())), i18n("Remove mapping to online account")) == KMessageBox::Yes) { MyMoneyFileTransaction ft; try { d->m_selectedAccount.setOnlineBankingSettings(MyMoneyKeyValueContainer()); // delete the kvp that is used in MyMoneyStatementReader too // we should really get rid of it, but since I don't know what it // is good for, I'll keep it around. (ipwizard) d->m_selectedAccount.deletePair("StatementKey"); MyMoneyFile::instance()->modifyAccount(d->m_selectedAccount); ft.commit(); // The mapping could disable the online task system onlineJobAdministration::instance()->updateOnlineTaskProperties(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to unmap account from online account: %1", e.what())); } } } void KMyMoneyApp::slotAccountMapOnline() { // no account selected if (d->m_selectedAccount.id().isEmpty()) return; // already an account mapped if (!d->m_selectedAccount.onlineBankingSettings().value("provider").isEmpty()) return; // check if user tries to map a brokerageAccount if (d->m_selectedAccount.name().contains(i18n(" (Brokerage)"))) { if (KMessageBox::warningContinueCancel(this, i18n("You try to map a brokerage account to an online account. This is usually not advisable. In general, the investment account should be mapped to the online account. Please cancel if you intended to map the investment account, continue otherwise"), i18n("Mapping brokerage account")) == KMessageBox::Cancel) { return; } } // if we have more than one provider let the user select the current provider QString provider; QMap::const_iterator it_p; switch (d->m_onlinePlugins.count()) { case 0: break; case 1: provider = d->m_onlinePlugins.begin().key(); break; default: { QMenu popup(this); popup.setTitle(i18n("Select online banking plugin")); // Populate the pick list with all the provider for (it_p = d->m_onlinePlugins.constBegin(); it_p != d->m_onlinePlugins.constEnd(); ++it_p) { popup.addAction(it_p.key())->setData(it_p.key()); } QAction *item = popup.actions()[0]; if (item) { popup.setActiveAction(item); } // cancelled if ((item = popup.exec(QCursor::pos(), item)) == 0) { return; } provider = item->data().toString(); } break; } if (provider.isEmpty()) return; // find the provider it_p = d->m_onlinePlugins.constFind(provider); if (it_p != d->m_onlinePlugins.constEnd()) { // plugin found, call it MyMoneyKeyValueContainer settings; if ((*it_p)->mapAccount(d->m_selectedAccount, settings)) { settings["provider"] = provider; MyMoneyAccount acc(d->m_selectedAccount); acc.setOnlineBankingSettings(settings); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyAccount(acc); ft.commit(); // The mapping could enable the online task system onlineJobAdministration::instance()->updateOnlineTaskProperties(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to map account to online account: %1", e.what())); } } } } void KMyMoneyApp::slotAccountUpdateOnlineAll() { QList accList; MyMoneyFile::instance()->accountList(accList); QList::iterator it_a; QMap::const_iterator it_p; d->m_statementResults.clear(); d->m_collectingStatements = true; // remove all those from the list, that don't have a 'provider' or the // provider is not currently present for (it_a = accList.begin(); it_a != accList.end();) { if ((*it_a).onlineBankingSettings().value("provider").isEmpty() || d->m_onlinePlugins.find((*it_a).onlineBankingSettings().value("provider")) == d->m_onlinePlugins.end()) { it_a = accList.erase(it_a); } else ++it_a; } QVector disabledActions {Action::AccountUpdate, Action::AccountUpdateMenu, Action::AccountUpdateAll}; foreach (const auto a, disabledActions) actionCollection()->action(s_Actions.value(a))->setEnabled(false); // now work on the remaining list of accounts int cnt = accList.count() - 1; for (it_a = accList.begin(); it_a != accList.end(); ++it_a) { it_p = d->m_onlinePlugins.constFind((*it_a).onlineBankingSettings().value("provider")); (*it_p)->updateAccount(*it_a, cnt != 0); --cnt; } d->m_collectingStatements = false; if (!d->m_statementResults.isEmpty()) KMessageBox::informationList(this, i18n("The statements have been processed with the following results:"), d->m_statementResults, i18n("Statement stats")); // re-enable the disabled actions slotUpdateActions(); } void KMyMoneyApp::slotAccountUpdateOnline() { // no account selected if (d->m_selectedAccount.id().isEmpty()) return; // no online account mapped if (d->m_selectedAccount.onlineBankingSettings().value("provider").isEmpty()) return; QVector disabledActions {Action::AccountUpdate, Action::AccountUpdateMenu, Action::AccountUpdateAll}; foreach (const auto a, disabledActions) actionCollection()->action(s_Actions.value(a))->setEnabled(false); // find the provider QMap::const_iterator it_p; it_p = d->m_onlinePlugins.constFind(d->m_selectedAccount.onlineBankingSettings().value("provider")); if (it_p != d->m_onlinePlugins.constEnd()) { // plugin found, call it d->m_collectingStatements = true; d->m_statementResults.clear(); (*it_p)->updateAccount(d->m_selectedAccount); d->m_collectingStatements = false; if (!d->m_statementResults.isEmpty()) KMessageBox::informationList(this, i18n("The statements have been processed with the following results:"), d->m_statementResults, i18n("Statement stats")); } // re-enable the disabled actions slotUpdateActions(); } void KMyMoneyApp::slotNewOnlineTransfer() { kOnlineTransferForm *transferForm = new kOnlineTransferForm(this); if (!d->m_selectedAccount.id().isEmpty()) { transferForm->setCurrentAccount(d->m_selectedAccount.id()); } connect(transferForm, SIGNAL(rejected()), transferForm, SLOT(deleteLater())); connect(transferForm, SIGNAL(acceptedForSave(onlineJob)), this, SLOT(slotOnlineJobSave(onlineJob))); connect(transferForm, SIGNAL(acceptedForSend(onlineJob)), this, SLOT(slotOnlineJobSend(onlineJob))); connect(transferForm, SIGNAL(accepted()), transferForm, SLOT(deleteLater())); transferForm->show(); } void KMyMoneyApp::slotEditOnlineJob(const QString jobId) { try { const onlineJob constJob = MyMoneyFile::instance()->getOnlineJob(jobId); slotEditOnlineJob(constJob); } catch (MyMoneyException&) { // Prevent a crash in very rare cases } } void KMyMoneyApp::slotEditOnlineJob(onlineJob job) { try { slotEditOnlineJob(onlineJobTyped(job)); } catch (MyMoneyException&) { } } void KMyMoneyApp::slotEditOnlineJob(const onlineJobTyped job) { kOnlineTransferForm *transferForm = new kOnlineTransferForm(this); transferForm->setOnlineJob(job); connect(transferForm, SIGNAL(rejected()), transferForm, SLOT(deleteLater())); connect(transferForm, SIGNAL(acceptedForSave(onlineJob)), this, SLOT(slotOnlineJobSave(onlineJob))); connect(transferForm, SIGNAL(acceptedForSend(onlineJob)), this, SLOT(slotOnlineJobSend(onlineJob))); connect(transferForm, SIGNAL(accepted()), transferForm, SLOT(deleteLater())); transferForm->show(); } void KMyMoneyApp::slotOnlineJobSave(onlineJob job) { MyMoneyFileTransaction fileTransaction; - if (job.id() == MyMoneyObject::emptyId()) + if (job.id().isEmpty()) MyMoneyFile::instance()->addOnlineJob(job); else MyMoneyFile::instance()->modifyOnlineJob(job); fileTransaction.commit(); } /** @todo when onlineJob queue is used, continue here */ void KMyMoneyApp::slotOnlineJobSend(onlineJob job) { MyMoneyFileTransaction fileTransaction; - if (job.id() == MyMoneyObject::emptyId()) + if (job.id().isEmpty()) MyMoneyFile::instance()->addOnlineJob(job); else MyMoneyFile::instance()->modifyOnlineJob(job); fileTransaction.commit(); QList jobList; jobList.append(job); slotOnlineJobSend(jobList); } void KMyMoneyApp::slotOnlineJobSend(QList jobs) { MyMoneyFile *const kmmFile = MyMoneyFile::instance(); QMultiMap jobsByPlugin; // Sort jobs by online plugin & lock them foreach (onlineJob job, jobs) { - Q_ASSERT(job.id() != MyMoneyObject::emptyId()); + Q_ASSERT(!job.id().isEmpty()); // find the provider const MyMoneyAccount originAcc = job.responsibleMyMoneyAccount(); job.setLock(); job.addJobMessage(onlineJobMessage(eMyMoney::OnlineJob::MessageType::Debug, "KMyMoneyApp::slotOnlineJobSend", "Added to queue for plugin '" + originAcc.onlineBankingSettings().value("provider") + '\'')); MyMoneyFileTransaction fileTransaction; kmmFile->modifyOnlineJob(job); fileTransaction.commit(); jobsByPlugin.insert(originAcc.onlineBankingSettings().value("provider"), job); } // Send onlineJobs to plugins QList usedPlugins = jobsByPlugin.keys(); std::sort(usedPlugins.begin(), usedPlugins.end()); const QList::iterator newEnd = std::unique(usedPlugins.begin(), usedPlugins.end()); usedPlugins.erase(newEnd, usedPlugins.end()); foreach (const QString& pluginKey, usedPlugins) { QMap::const_iterator it_p = d->m_onlinePlugins.constFind(pluginKey); if (it_p != d->m_onlinePlugins.constEnd()) { // plugin found, call it KMyMoneyPlugin::OnlinePluginExtended *pluginExt = dynamic_cast< KMyMoneyPlugin::OnlinePluginExtended* >(*it_p); if (pluginExt == 0) { qWarning("Job given for plugin which is not an extended plugin"); continue; } //! @fixme remove debug message qDebug() << "Sending " << jobsByPlugin.count(pluginKey) << " job(s) to online plugin " << pluginKey; QList jobsToExecute = jobsByPlugin.values(pluginKey); QList executedJobs = jobsToExecute; pluginExt->sendOnlineJob(executedJobs); // Save possible changes of the online job and remove lock MyMoneyFileTransaction fileTransaction; foreach (onlineJob job, executedJobs) { fileTransaction.restart(); job.setLock(false); kmmFile->modifyOnlineJob(job); fileTransaction.commit(); } if (Q_UNLIKELY(executedJobs.size() != jobsToExecute.size())) { // OnlinePlugin did not return all jobs qWarning() << "Error saving send online tasks. After restart you should see at minimum all successfully executed jobs marked send. Imperfect plugin: " << pluginExt->objectName(); } } else { qWarning() << "Error, got onlineJob for an account without online plugin."; /** @FIXME can this actually happen? */ } } } void KMyMoneyApp::slotRemoveJob() { } void KMyMoneyApp::slotEditJob() { } void KMyMoneyApp::slotOnlineJobLog() { QStringList jobIds = d->m_myMoneyView->getOnlineJobOutbox()->selectedOnlineJobs(); slotOnlineJobLog(jobIds); } void KMyMoneyApp::slotOnlineJobLog(const QStringList& onlineJobIds) { onlineJobMessagesView *const dialog = new onlineJobMessagesView(); onlineJobMessagesModel *const model = new onlineJobMessagesModel(dialog); model->setOnlineJob(MyMoneyFile::instance()->getOnlineJob(onlineJobIds.first())); dialog->setModel(model); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); // Note: Objects are not deleted here, Qt's parent-child system has to do that. } void KMyMoneyApp::setHolidayRegion(const QString& holidayRegion) { #ifdef KF5Holidays_FOUND //since the cost of updating the cache is now not negligible //check whether the region has been modified if (!d->m_holidayRegion || d->m_holidayRegion->regionCode() != holidayRegion) { // Delete the previous holidayRegion before creating a new one. delete d->m_holidayRegion; // Create a new holidayRegion. d->m_holidayRegion = new KHolidays::HolidayRegion(holidayRegion); //clear and update the holiday cache preloadHolidays(); } #else Q_UNUSED(holidayRegion); #endif } bool KMyMoneyApp::isProcessingDate(const QDate& date) const { #ifdef KF5Holidays_FOUND if (!d->m_processingDays.testBit(date.dayOfWeek())) return false; if (!d->m_holidayRegion || !d->m_holidayRegion->isValid()) return true; //check first whether it's already in cache if (d->m_holidayMap.contains(date)) { return d->m_holidayMap.value(date, true); } else { bool processingDay = !d->m_holidayRegion->isHoliday(date); d->m_holidayMap.insert(date, processingDay); return processingDay; } #else Q_UNUSED(date); return true; #endif } void KMyMoneyApp::preloadHolidays() { #ifdef KF5Holidays_FOUND //clear the cache before loading d->m_holidayMap.clear(); //only do this if it is a valid region if (d->m_holidayRegion && d->m_holidayRegion->isValid()) { //load holidays for the forecast days plus 1 cycle, to be on the safe side int forecastDays = KMyMoneyGlobalSettings::forecastDays() + KMyMoneyGlobalSettings::forecastAccountCycle(); QDate endDate = QDate::currentDate().addDays(forecastDays); //look for holidays for the next 2 years as a minimum. That should give a good margin for the cache if (endDate < QDate::currentDate().addYears(2)) endDate = QDate::currentDate().addYears(2); KHolidays::Holiday::List holidayList = d->m_holidayRegion->holidays(QDate::currentDate(), endDate); KHolidays::Holiday::List::const_iterator holiday_it; for (holiday_it = holidayList.constBegin(); holiday_it != holidayList.constEnd(); ++holiday_it) { for (QDate holidayDate = (*holiday_it).observedStartDate(); holidayDate <= (*holiday_it).observedEndDate(); holidayDate = holidayDate.addDays(1)) d->m_holidayMap.insert(holidayDate, false); } for (QDate date = QDate::currentDate(); date <= endDate; date = date.addDays(1)) { //if it is not a processing day, set it to false if (!d->m_processingDays.testBit(date.dayOfWeek())) { d->m_holidayMap.insert(date, false); } else if (!d->m_holidayMap.contains(date)) { //if it is not a holiday nor a weekend, it is a processing day d->m_holidayMap.insert(date, true); } } } #endif } KMStatus::KMStatus(const QString &text) { m_prevText = kmymoney->slotStatusMsg(text); } KMStatus::~KMStatus() { kmymoney->slotStatusMsg(m_prevText); } void KMyMoneyApp::Private::unlinkStatementXML() { QDir d(KMyMoneySettings::logPath(), "kmm-statement*"); for (uint i = 0; i < d.count(); ++i) { qDebug("Remove %s", qPrintable(d[i])); d.remove(KMyMoneySettings::logPath() + QString("/%1").arg(d[i])); } m_statementXMLindex = 0; } void KMyMoneyApp::Private::closeFile() { q->slotSelectAccount(MyMoneyAccount()); q->slotSelectInstitution(MyMoneyInstitution()); q->slotSelectInvestment(MyMoneyAccount()); q->slotSelectSchedule(); q->slotSelectCurrency(); q->slotSelectBudget(QList()); q->slotSelectPayees(QList()); q->slotSelectTags(QList()); q->slotSelectTransactions(KMyMoneyRegister::SelectedTransactions()); m_reconciliationAccount = MyMoneyAccount(); m_myMoneyView->finishReconciliation(m_reconciliationAccount); m_myMoneyView->closeFile(); m_fileName = QUrl(); q->updateCaption(); // just create a new balance warning object delete m_balanceWarning; m_balanceWarning = new KBalanceWarning(q); emit q->fileLoaded(m_fileName); } diff --git a/kmymoney/mymoney/mymoneyaccount.cpp b/kmymoney/mymoney/mymoneyaccount.cpp index 3f4291f17..fb11ca899 100644 --- a/kmymoney/mymoney/mymoneyaccount.cpp +++ b/kmymoney/mymoney/mymoneyaccount.cpp @@ -1,746 +1,740 @@ /*************************************************************************** mymoneyaccount.cpp ------------------- copyright : (C) 2000 by Michael Edwardes (C) 2002 by Thomas Baumgart (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. * * * ***************************************************************************/ #include "mymoneyaccount.h" #include "mymoneyaccount_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyutils.h" #include "mymoneyexception.h" #include "mymoneysplit.h" #include "mymoneyfile.h" #include "mymoneysecurity.h" #include "mymoneyinstitution.h" #include "mymoneypayee.h" #include "payeeidentifier/payeeidentifiertyped.h" #include "payeeidentifier/ibanandbic/ibanbic.h" #include "payeeidentifier/nationalaccount/nationalaccount.h" #include "mymoneystoragenames.h" #include "icons/icons.h" using namespace MyMoneyStorageNodes; using namespace Icons; MyMoneyAccount::MyMoneyAccount() : - MyMoneyObject(), - MyMoneyKeyValueContainer(), - d_ptr(new MyMoneyAccountPrivate) + MyMoneyObject(*new MyMoneyAccountPrivate), + MyMoneyKeyValueContainer() { } MyMoneyAccount::MyMoneyAccount(const QDomElement& node) : - MyMoneyObject(node), - MyMoneyKeyValueContainer(node.elementsByTagName(nodeNames[nnKeyValuePairs]).item(0).toElement()), - d_ptr(new MyMoneyAccountPrivate) + MyMoneyObject(*new MyMoneyAccountPrivate, node), + MyMoneyKeyValueContainer(node.elementsByTagName(nodeNames[nnKeyValuePairs]).item(0).toElement()) { if (nodeNames[nnAccount] != node.tagName()) throw MYMONEYEXCEPTION("Node was not ACCOUNT"); Q_D(MyMoneyAccount); setName(node.attribute(d->getAttrName(Account::Attribute::Name))); // qDebug("Reading information for account %s", acc.name().data()); setParentAccountId(MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Account::Attribute::ParentAccount)))); setLastModified(MyMoneyUtils::stringToDate(MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Account::Attribute::LastModified))))); setLastReconciliationDate(MyMoneyUtils::stringToDate(MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Account::Attribute::LastReconciled))))); if (!d->m_lastReconciliationDate.isValid()) { // for some reason, I was unable to access our own kvp at this point through // the value() method. It always returned empty strings. The workaround for // this is to construct a local kvp the same way as we have done before and // extract the value from it. // // Since we want to get rid of the lastStatementDate record anyway, this seems // to be ok for now. (ipwizard - 2008-08-14) QString txt = MyMoneyKeyValueContainer(node.elementsByTagName(nodeNames[nnKeyValuePairs]).item(0).toElement()).value("lastStatementDate"); if (!txt.isEmpty()) { setLastReconciliationDate(QDate::fromString(txt, Qt::ISODate)); } } setInstitutionId(MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Account::Attribute::Institution)))); setNumber(MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Account::Attribute::Number)))); setOpeningDate(MyMoneyUtils::stringToDate(MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Account::Attribute::Opened))))); setCurrencyId(MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Account::Attribute::Currency)))); QString tmp = MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Account::Attribute::Type))); bool bOK = false; int type = tmp.toInt(&bOK); if (bOK) { setAccountType(static_cast(type)); } else { qWarning("XMLREADER: Account %s had invalid or no account type information.", qPrintable(name())); } if (node.hasAttribute(d->getAttrName(Account::Attribute::OpeningBalance))) { if (!MyMoneyMoney(node.attribute(d->getAttrName(Account::Attribute::OpeningBalance))).isZero()) { QString msg = i18n("Account %1 contains an opening balance. Please use KMyMoney version 0.8 or later and earlier than version 0.9 to correct the problem.", d->m_name); throw MYMONEYEXCEPTION(msg); } } setDescription(node.attribute(d->getAttrName(Account::Attribute::Description))); - m_id = MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Account::Attribute::ID))); + d->m_id = MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Account::Attribute::ID))); // qDebug("Account %s has id of %s, type of %d, parent is %s.", acc.name().data(), id.data(), type, acc.parentAccountId().data()); // Process any Sub-Account information found inside the account entry. d->m_accountList.clear(); QDomNodeList nodeList = node.elementsByTagName(d->getElName(Account::Element::SubAccounts)); if (nodeList.count() > 0) { nodeList = nodeList.item(0).toElement().elementsByTagName(d->getElName(Account::Element::SubAccount)); for (int i = 0; i < nodeList.count(); ++i) { addAccountId(QString(nodeList.item(i).toElement().attribute(d->getAttrName(Account::Attribute::ID)))); } } nodeList = node.elementsByTagName(d->getElName(Account::Element::OnlineBanking)); if (nodeList.count() > 0) { QDomNamedNodeMap attributes = nodeList.item(0).toElement().attributes(); for (int i = 0; i < attributes.count(); ++i) { const QDomAttr& it_attr = attributes.item(i).toAttr(); d->m_onlineBankingSettings.setValue(it_attr.name(), it_attr.value()); } } // Up to and including version 4.6.6 the new account dialog stored the iban in the kvp-key "IBAN". // But the rest of the software uses "iban". So correct this: if (!value("IBAN").isEmpty()) { // If "iban" was not set, set it now. If it is set, the user reseted it already, so remove // the garbage. if (value(d->getAttrName(Account::Attribute::IBAN)).isEmpty()) setValue(d->getAttrName(Account::Attribute::IBAN), value("IBAN")); deletePair("IBAN"); } } MyMoneyAccount::MyMoneyAccount(const MyMoneyAccount& other) : - MyMoneyObject(other.id()), - MyMoneyKeyValueContainer(other), - d_ptr(new MyMoneyAccountPrivate(*other.d_func())) + MyMoneyObject(*new MyMoneyAccountPrivate(*other.d_func()), other.id()), + MyMoneyKeyValueContainer(other) { } MyMoneyAccount::MyMoneyAccount(const QString& id, const MyMoneyAccount& other) : - MyMoneyObject(id), - MyMoneyKeyValueContainer(other), - d_ptr(new MyMoneyAccountPrivate(*other.d_func())) + MyMoneyObject(*new MyMoneyAccountPrivate(*other.d_func()), id), + MyMoneyKeyValueContainer(other) { } MyMoneyAccount::~MyMoneyAccount() { - Q_D(MyMoneyAccount); - delete d; } void MyMoneyAccount::touch() { setLastModified(QDate::currentDate()); } eMyMoney::Account::Type MyMoneyAccount::accountType() const { Q_D(const MyMoneyAccount); return d->m_accountType; } void MyMoneyAccount::setAccountType(const Account::Type type) { Q_D(MyMoneyAccount); d->m_accountType = type; } QString MyMoneyAccount::institutionId() const { Q_D(const MyMoneyAccount); return d->m_institution; } void MyMoneyAccount::setInstitutionId(const QString& id) { Q_D(MyMoneyAccount); d->m_institution = id; } QString MyMoneyAccount::name() const { Q_D(const MyMoneyAccount); return d->m_name; } void MyMoneyAccount::setName(const QString& name) { Q_D(MyMoneyAccount); d->m_name = name; } QString MyMoneyAccount::number() const { Q_D(const MyMoneyAccount); return d->m_number; } void MyMoneyAccount::setNumber(const QString& number) { Q_D(MyMoneyAccount); d->m_number = number; } QString MyMoneyAccount::description() const { Q_D(const MyMoneyAccount); return d->m_description; } void MyMoneyAccount::setDescription(const QString& desc) { Q_D(MyMoneyAccount); d->m_description = desc; } QDate MyMoneyAccount::openingDate() const { Q_D(const MyMoneyAccount); return d->m_openingDate; } void MyMoneyAccount::setOpeningDate(const QDate& date) { Q_D(MyMoneyAccount); d->m_openingDate = date; } QDate MyMoneyAccount::lastReconciliationDate() const { Q_D(const MyMoneyAccount); return d->m_lastReconciliationDate; } void MyMoneyAccount::setLastReconciliationDate(const QDate& date) { Q_D(MyMoneyAccount); // FIXME: for a limited time (maybe until we delivered 1.0) we // keep the last reconciliation date also in the KVP for backward // compatibility. After that, the setValue() statemetn should be removed // and the XML ctor should remove the value completely from the KVP setValue("lastStatementDate", date.toString(Qt::ISODate)); d->m_lastReconciliationDate = date; } QDate MyMoneyAccount::lastModified() const { Q_D(const MyMoneyAccount); return d->m_lastModified; } void MyMoneyAccount::setLastModified(const QDate& date) { Q_D(MyMoneyAccount); d->m_lastModified = date; } QString MyMoneyAccount::parentAccountId() const { Q_D(const MyMoneyAccount); return d->m_parentAccount; } void MyMoneyAccount::setParentAccountId(const QString& parent) { Q_D(MyMoneyAccount); d->m_parentAccount = parent; } const QStringList& MyMoneyAccount::accountList() const { Q_D(const MyMoneyAccount); return d->m_accountList; } int MyMoneyAccount::accountCount() const { Q_D(const MyMoneyAccount); return d->m_accountList.count(); } void MyMoneyAccount::addAccountId(const QString& account) { Q_D(MyMoneyAccount); if (!d->m_accountList.contains(account)) d->m_accountList += account; } void MyMoneyAccount::removeAccountIds() { Q_D(MyMoneyAccount); d->m_accountList.clear(); } void MyMoneyAccount::removeAccountId(const QString& account) { Q_D(MyMoneyAccount); const auto pos = d->m_accountList.indexOf(account); if (pos != -1) d->m_accountList.removeAt(pos); } bool MyMoneyAccount::operator == (const MyMoneyAccount& right) const { Q_D(const MyMoneyAccount); auto d2 = static_cast(right.d_func()); return (MyMoneyKeyValueContainer::operator==(right) && MyMoneyObject::operator==(right) && (d->m_accountList == d2->m_accountList) && (d->m_accountType == d2->m_accountType) && (d->m_lastModified == d2->m_lastModified) && (d->m_lastReconciliationDate == d2->m_lastReconciliationDate) && ((d->m_name.length() == 0 && d2->m_name.length() == 0) || (d->m_name == d2->m_name)) && ((d->m_number.length() == 0 && d2->m_number.length() == 0) || (d->m_number == d2->m_number)) && ((d->m_description.length() == 0 && d2->m_description.length() == 0) || (d->m_description == d2->m_description)) && (d->m_openingDate == d2->m_openingDate) && (d->m_parentAccount == d2->m_parentAccount) && (d->m_currencyId == d2->m_currencyId) && (d->m_institution == d2->m_institution)); } Account::Type MyMoneyAccount::accountGroup() const { Q_D(const MyMoneyAccount); switch (d->m_accountType) { case Account::Type::Checkings: case Account::Type::Savings: case Account::Type::Cash: case Account::Type::Currency: case Account::Type::Investment: case Account::Type::MoneyMarket: case Account::Type::CertificateDep: case Account::Type::AssetLoan: case Account::Type::Stock: return Account::Type::Asset; case Account::Type::CreditCard: case Account::Type::Loan: return Account::Type::Liability; default: return d->m_accountType; } } QString MyMoneyAccount::currencyId() const { Q_D(const MyMoneyAccount); return d->m_currencyId; } void MyMoneyAccount::setCurrencyId(const QString& id) { Q_D(MyMoneyAccount); d->m_currencyId = id; } bool MyMoneyAccount::isAssetLiability() const { return accountGroup() == Account::Type::Asset || accountGroup() == Account::Type::Liability; } bool MyMoneyAccount::isIncomeExpense() const { return accountGroup() == Account::Type::Income || accountGroup() == Account::Type::Expense; } bool MyMoneyAccount::isLoan() const { return accountType() == Account::Type::Loan || accountType() == Account::Type::AssetLoan; } bool MyMoneyAccount::isInvest() const { return accountType() == Account::Type::Stock; } bool MyMoneyAccount::isLiquidAsset() const { return accountType() == Account::Type::Checkings || accountType() == Account::Type::Savings || accountType() == Account::Type::Cash; } bool MyMoneyAccount::isCostCenterRequired() const { return value("CostCenter").toLower() == QLatin1String("yes"); } void MyMoneyAccount::setCostCenterRequired(bool required) { if(required) { setValue("CostCenter", "yes"); } else { deletePair("CostCenter"); } } template<> QList< payeeIdentifierTyped< ::payeeIdentifiers::ibanBic> > MyMoneyAccount::payeeIdentifiersByType() const { Q_D(const MyMoneyAccount); payeeIdentifierTyped ident = payeeIdentifierTyped(new payeeIdentifiers::ibanBic); ident->setIban(value(d->getAttrName(Account::Attribute::IBAN))); if (!institutionId().isEmpty()) { const MyMoneyInstitution institution = MyMoneyFile::instance()->institution(institutionId()); ident->setBic(institution.value(d->getAttrName(Account::Attribute::BIC))); } ident->setOwnerName(MyMoneyFile::instance()->user().name()); QList< payeeIdentifierTyped > typedList; typedList << ident; return typedList; } void MyMoneyAccount::writeXML(QDomDocument& document, QDomElement& parent) const { - QDomElement el = document.createElement(nodeNames[nnAccount]); - - writeBaseXML(document, el); + auto el = document.createElement(nodeNames[nnAccount]); Q_D(const MyMoneyAccount); + d->writeBaseXML(document, el); + el.setAttribute(d->getAttrName(Account::Attribute::ParentAccount), parentAccountId()); el.setAttribute(d->getAttrName(Account::Attribute::LastReconciled), MyMoneyUtils::dateToString(lastReconciliationDate())); el.setAttribute(d->getAttrName(Account::Attribute::LastModified), MyMoneyUtils::dateToString(lastModified())); el.setAttribute(d->getAttrName(Account::Attribute::Institution), institutionId()); el.setAttribute(d->getAttrName(Account::Attribute::Opened), MyMoneyUtils::dateToString(openingDate())); el.setAttribute(d->getAttrName(Account::Attribute::Number), number()); // el.setAttribute(getAttrName(anOpeningBalance), openingBalance().toString()); el.setAttribute(d->getAttrName(Account::Attribute::Type), (int)accountType()); el.setAttribute(d->getAttrName(Account::Attribute::Name), name()); el.setAttribute(d->getAttrName(Account::Attribute::Description), description()); if (!currencyId().isEmpty()) el.setAttribute(d->getAttrName(Account::Attribute::Currency), currencyId()); //Add in subaccount information, if this account has subaccounts. if (accountCount()) { QDomElement subAccounts = document.createElement(d->getElName(Account::Element::SubAccounts)); foreach (const auto accountID, accountList()) { QDomElement temp = document.createElement(d->getElName(Account::Element::SubAccount)); temp.setAttribute(d->getAttrName(Account::Attribute::ID), accountID); subAccounts.appendChild(temp); } el.appendChild(subAccounts); } // Write online banking settings if (d->m_onlineBankingSettings.pairs().count()) { QDomElement onlinesettings = document.createElement(d->getElName(Account::Element::OnlineBanking)); QMap::const_iterator it_key = d->m_onlineBankingSettings.pairs().begin(); while (it_key != d->m_onlineBankingSettings.pairs().end()) { onlinesettings.setAttribute(it_key.key(), it_key.value()); ++it_key; } el.appendChild(onlinesettings); } // FIXME drop the lastStatementDate record from the KVP when it is // not stored there after setLastReconciliationDate() has been changed // See comment there when this will happen // deletePair("lastStatementDate"); //Add in Key-Value Pairs for accounts. MyMoneyKeyValueContainer::writeXML(document, el); parent.appendChild(el); } bool MyMoneyAccount::hasReferenceTo(const QString& id) const { Q_D(const MyMoneyAccount); return (id == d->m_institution) || (id == d->m_parentAccount) || (id == d->m_currencyId); } void MyMoneyAccount::setOnlineBankingSettings(const MyMoneyKeyValueContainer& values) { Q_D(MyMoneyAccount); d->m_onlineBankingSettings = values; } const MyMoneyKeyValueContainer& MyMoneyAccount::onlineBankingSettings() const { Q_D(const MyMoneyAccount); return d->m_onlineBankingSettings; } void MyMoneyAccount::setClosed(bool closed) { if (closed) setValue("mm-closed", "yes"); else deletePair("mm-closed"); } bool MyMoneyAccount::isClosed() const { return !(value("mm-closed").isEmpty()); } int MyMoneyAccount::fraction(const MyMoneySecurity& sec) const { Q_D(const MyMoneyAccount); int fraction; if (d->m_accountType == Account::Type::Cash) fraction = sec.smallestCashFraction(); else fraction = sec.smallestAccountFraction(); return fraction; } int MyMoneyAccount::fraction(const MyMoneySecurity& sec) { Q_D(MyMoneyAccount); if (d->m_accountType == Account::Type::Cash) d->m_fraction = sec.smallestCashFraction(); else d->m_fraction = sec.smallestAccountFraction(); return d->m_fraction; } int MyMoneyAccount::fraction() const { Q_D(const MyMoneyAccount); return d->m_fraction; } bool MyMoneyAccount::isCategory() const { Q_D(const MyMoneyAccount); return d->m_accountType == Account::Type::Income || d->m_accountType == Account::Type::Expense; } QString MyMoneyAccount::brokerageName() const { Q_D(const MyMoneyAccount); if (d->m_accountType == Account::Type::Investment) return QString("%1 (%2)").arg(d->m_name, i18nc("Brokerage (suffix for account names)", "Brokerage")); return d->m_name; } MyMoneyMoney MyMoneyAccount::balance() const { Q_D(const MyMoneyAccount); return d->m_balance; } void MyMoneyAccount::adjustBalance(const MyMoneySplit& s, bool reverse) { Q_D(MyMoneyAccount); if (s.action() == MyMoneySplit::ActionSplitShares) { if (reverse) d->m_balance = d->m_balance / s.shares(); else d->m_balance = d->m_balance * s.shares(); } else { if (reverse) d->m_balance -= s.shares(); else d->m_balance += s.shares(); } } void MyMoneyAccount::setBalance(const MyMoneyMoney& val) { Q_D(MyMoneyAccount); d->m_balance = val; } QPixmap MyMoneyAccount::accountPixmap(const bool reconcileFlag, const int size) const { static const QHash accToIco { {Account::Type::Asset, Icon::ViewAsset}, {Account::Type::Investment, Icon::ViewStock}, {Account::Type::Stock, Icon::ViewStock}, {Account::Type::MoneyMarket, Icon::ViewStock}, {Account::Type::Checkings, Icon::ViewChecking}, {Account::Type::Savings, Icon::ViewSaving}, {Account::Type::AssetLoan, Icon::ViewLoanAsset}, {Account::Type::Loan, Icon::ViewLoan}, {Account::Type::CreditCard, Icon::ViewCreditCard}, {Account::Type::Asset, Icon::ViewAsset}, {Account::Type::Cash, Icon::ViewCash}, {Account::Type::Income, Icon::ViewIncome}, {Account::Type::Expense, Icon::ViewExpense}, {Account::Type::Equity, Icon::ViewEquity} }; Icon ixIcon = accToIco.value(accountType(), Icon::ViewLiability); QString kyIcon = g_Icons.value(ixIcon) + QString::number(size); QPixmap pxIcon; if (!QPixmapCache::find(kyIcon, pxIcon)) { pxIcon = QIcon::fromTheme(g_Icons.value(ixIcon)).pixmap(size); // Qt::AA_UseHighDpiPixmaps (in Qt 5.7) doesn't return highdpi pixmap QPixmapCache::insert(kyIcon, pxIcon); } if (isClosed()) ixIcon = Icon::AccountClosed; else if (reconcileFlag) ixIcon = Icon::FlagGreen; else if (!onlineBankingSettings().value("provider").isEmpty()) ixIcon = Icon::Download; else return pxIcon; QPixmap pxOverlay = QIcon::fromTheme(g_Icons[ixIcon]).pixmap(size); QPainter pxPainter(&pxIcon); const QSize szIcon = pxIcon.size(); pxPainter.drawPixmap(szIcon.width() / 2, szIcon.height() / 2, szIcon.width() / 2, szIcon.height() / 2, pxOverlay); return pxIcon; } QString MyMoneyAccount::accountTypeToString(const Account::Type accountType) { switch (accountType) { case Account::Type::Checkings: return i18nc("Account type", "Checking"); case Account::Type::Savings: return i18nc("Account type", "Savings"); case Account::Type::CreditCard: return i18nc("Account type", "Credit Card"); case Account::Type::Cash: return i18nc("Account type", "Cash"); case Account::Type::Loan: return i18nc("Account type", "Loan"); case Account::Type::CertificateDep: return i18nc("Account type", "Certificate of Deposit"); case Account::Type::Investment: return i18nc("Account type", "Investment"); case Account::Type::MoneyMarket: return i18nc("Account type", "Money Market"); case Account::Type::Asset: return i18nc("Account type", "Asset"); case Account::Type::Liability: return i18nc("Account type", "Liability"); case Account::Type::Currency: return i18nc("Account type", "Currency"); case Account::Type::Income: return i18nc("Account type", "Income"); case Account::Type::Expense: return i18nc("Account type", "Expense"); case Account::Type::AssetLoan: return i18nc("Account type", "Investment Loan"); case Account::Type::Stock: return i18nc("Account type", "Stock"); case Account::Type::Equity: return i18nc("Account type", "Equity"); default: return i18nc("Account type", "Unknown"); } } bool MyMoneyAccount::addReconciliation(const QDate& date, const MyMoneyMoney& amount) { Q_D(MyMoneyAccount); d->m_reconciliationHistory[date] = amount; QString history, sep; QMap::const_iterator it; for (it = d->m_reconciliationHistory.constBegin(); it != d->m_reconciliationHistory.constEnd(); ++it) { history += QString("%1%2:%3").arg(sep, it.key().toString(Qt::ISODate), (*it).toString()); sep = QLatin1Char(';'); } setValue("reconciliationHistory", history); return true; } const QMap& MyMoneyAccount::reconciliationHistory() { Q_D(MyMoneyAccount); // check if the internal history member is already loaded if (d->m_reconciliationHistory.count() == 0 && !value("reconciliationHistory").isEmpty()) { QStringList entries = value("reconciliationHistory").split(';'); foreach (const QString& entry, entries) { QStringList parts = entry.split(':'); QDate date = QDate::fromString(parts[0], Qt::ISODate); MyMoneyMoney amount(parts[1]); if (parts.count() == 2 && date.isValid()) { d->m_reconciliationHistory[date] = amount; } } } return d->m_reconciliationHistory; } /** * @todo Improve setting of country for nationalAccount */ QList< payeeIdentifier > MyMoneyAccount::payeeIdentifiers() const { Q_D(const MyMoneyAccount); QList< payeeIdentifier > list; MyMoneyFile* file = MyMoneyFile::instance(); // Iban & Bic if (!value(d->getAttrName(Account::Attribute::IBAN)).isEmpty()) { payeeIdentifierTyped iban(new payeeIdentifiers::ibanBic); iban->setIban(value(d->getAttrName(Account::Attribute::IBAN))); iban->setBic(file->institution(institutionId()).value(d->getAttrName(Account::Attribute::BIC))); iban->setOwnerName(file->user().name()); list.append(iban); } // National Account number if (!number().isEmpty()) { payeeIdentifierTyped national(new payeeIdentifiers::nationalAccount); national->setAccountNumber(number()); national->setBankCode(file->institution(institutionId()).sortcode()); if (file->user().state().length() == 2) national->setCountry(file->user().state()); national->setOwnerName(file->user().name()); list.append(national); } return list; } diff --git a/kmymoney/mymoney/mymoneyaccount.h b/kmymoney/mymoney/mymoneyaccount.h index 9341f9d44..0f416bbe8 100644 --- a/kmymoney/mymoney/mymoneyaccount.h +++ b/kmymoney/mymoney/mymoneyaccount.h @@ -1,610 +1,608 @@ /*************************************************************************** mymoneyaccount.h ------------------- copyright : (C) 2002 by Thomas Baumgart (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 MYMONEYACCOUNT_H #define MYMONEYACCOUNT_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneykeyvaluecontainer.h" #include "mymoneyobject.h" #include "kmm_mymoney_export.h" #include "mymoneyunittestable.h" class QString; class QDate; class QPixmap; class QDomElement; class MyMoneySecurity; class MyMoneyMoney; class MyMoneySplit; class payeeIdentifier; namespace payeeIdentifiers { class ibanBic; } namespace eMyMoney { namespace Account { enum class Type; } } template class payeeIdentifierTyped; /** * A representation of an account. * This object represents any type of account, those held at an * institution as well as the accounts used for double entry * accounting. * * Currently, the following account types are known: * * @li Unknown * @li Checkings * @li Savings * @li Cash * @li CreditCard * @li Loan (collected) * @li CertificateDep * @li Investment * @li MoneyMarket * @li Currency * @li Asset * @li Liability * @li Income * @li Expense * @li Loan (given) * @li Stock * @li Equity * * @see MyMoneyInstitution * @see MyMoneyFile * * @author Michael Edwardes 2000-2001 * @author Thomas Baumgart 2002 * @author Łukasz Wojniłowicz 2017 * **/ class MyMoneyAccountPrivate; class KMM_MYMONEY_EXPORT MyMoneyAccount : public MyMoneyObject, public MyMoneyKeyValueContainer /*, public MyMoneyPayeeIdentifierContainer */ { - Q_DECLARE_PRIVATE(MyMoneyAccount) - MyMoneyAccountPrivate* d_ptr; + Q_DECLARE_PRIVATE_D(MyMoneyObject::d_ptr, MyMoneyAccount) KMM_MYMONEY_UNIT_TESTABLE public: /** * This is the constructor for a new empty account */ MyMoneyAccount(); /** * This is the constructor for a new account known to the current file * This is the only constructor that will set the attribute m_openingDate * to a correct value. * * @param id id assigned to the account * @param right account definition */ MyMoneyAccount(const QString& id, const MyMoneyAccount& other); /** * This is the constructor for an account that is described by a * QDomElement (e.g. from a file). * * @param el const reference to the QDomElement from which to * create the object */ explicit MyMoneyAccount(const QDomElement& el); MyMoneyAccount(const MyMoneyAccount & other); MyMoneyAccount(MyMoneyAccount && other); MyMoneyAccount & operator=(MyMoneyAccount other); friend void swap(MyMoneyAccount& first, MyMoneyAccount& second); /** * This is the destructor for any MyMoneyAccount object */ ~MyMoneyAccount(); /** * This operator tests for equality of two MyMoneyAccount objects */ bool operator == (const MyMoneyAccount &) const; /** * This converts the account type into one of the four * major account types liability, asset, expense or income. * * The current assignment is as follows: * * - Asset * - Asset * - Checkings * - Savings * - Cash * - Currency * - Investment * - MoneyMarket * - CertificateDep * - AssetLoan * - Stock * * - Liability * - Liability * - CreditCard * - Loan * * - Income * - Income * * - Expense * - Expense * * @return accountTypeE of major account type */ eMyMoney::Account::Type accountGroup() const; /** * This method returns the id of the MyMoneyInstitution object this account * belongs to. * @return id of MyMoneyInstitution object. QString() if it is * an internal account * @see setInstitution */ QString institutionId() const; /** * This method is used to set the id of the institution this account * belongs to. * * @param id id of the institution this account belongs to * @see institution */ void setInstitutionId(const QString& id); /** * This method returns the name of the account * @return name of account * @see setName() */ QString name() const; /** * This method is used to set the name of the account * @param name name of the account * @see name */ void setName(const QString& name); /** * This method returns the number of the account at the institution * @return number of account at the institution * @see setNumber */ QString number() const; /** * This method is used to set the number of the account at the institution * @param number number of the account * @see number */ void setNumber(const QString& number); /** * This method returns the descriptive text of the account. * @return description of account * @see setDescription */ QString description() const; /** * This method is used to set the descriptive text of the account * * @param desc text that serves as description * @see setDescription */ void setDescription(const QString& desc); /** * This method returns the opening date of this account * @return date of opening of this account as const QDate value * @see setOpeningDate() */ QDate openingDate() const; /** * This method is used to set the opening date information of an * account. * * @param date QDate of opening date * @see openingDate */ void setOpeningDate(const QDate& date); /** * This method returns the date of the last reconciliation of this account * @return date of last reconciliation as const QDate value * @see setLastReconciliationDate */ QDate lastReconciliationDate() const; /** * This method is used to set the date of the last reconciliation * of an account. * @param date QDate of last reconciliation * @see lastReconciliationDate */ void setLastReconciliationDate(const QDate& date); /** * This method returns the date the account was last modified * @return date of last modification as const QDate value * @see setLastModified */ QDate lastModified() const; /** * This method is used to modify the date of the last * modification access. * @param date date of last modification * @see lastModified */ void setLastModified(const QDate& date); /** * This method is used to return the ID of the parent account * @return QString with the ID of the parent of this account */ QString parentAccountId() const; /** * This method is used to set a new parent account id * @param parent QString reference to new parent account */ void setParentAccountId(const QString& parent); /** * This method returns the list of the account id's of * subordinate accounts * @return QStringList account ids */ const QStringList& accountList() const; /** * This method returns the number of entries in the m_accountList * @return number of entries in the accountList */ int accountCount() const; /** * This method is used to add an account id as sub-ordinate account * @param account const QString reference to account ID */ void addAccountId(const QString& account); /** * This method is used to remove an account from the list of * sub-ordinate accounts. * @param account const QString reference to account ID to be removed */ void removeAccountId(const QString& account); /** * This method is used to remove all accounts from the list of * sub-ordinate accounts. */ void removeAccountIds(); /** * Return the stored account identifiers * * @internal This method is temporary until MyMoneyAccount is a MyMoneyPayeeIdentifierContainer. But before this * can happen the account numbers and iban kvp data must be moved there. */ QList< payeeIdentifier > payeeIdentifiers() const; /** * @see MyMoneyPayeeIdentifierContainer::payeeIdentifiersByType() */ template< class type > QList< ::payeeIdentifierTyped > payeeIdentifiersByType() const; /** * This method is used to update m_lastModified to the current date */ void touch(); /** * This method returns the type of the account. */ eMyMoney::Account::Type accountType() const; /** * This method is used to change the account type * * @param type account type */ void setAccountType(const eMyMoney::Account::Type type); /** * This method retrieves the id of the currency used with this account. * If the return value is empty, the base currency should be used. * * @return id of currency */ QString currencyId() const; /** * This method sets the id of the currency used with this account. * * @param id ID of currency to be associated with this account. */ void setCurrencyId(const QString& id); void writeXML(QDomDocument& document, QDomElement& parent) const override; /** * This method checks if a reference to the given object exists. It returns, * a @p true if the object is referencing the one requested by the * parameter @p id. If it does not, this method returns @p false. * * @param id id of the object to be checked for references * @retval true This object references object with id @p id. * @retval false This object does not reference the object with id @p id. */ bool hasReferenceTo(const QString& id) const override; /** * This member returns the balance of this account based on * all transactions stored in the journal. */ MyMoneyMoney balance() const; /** * This method adjusts the balance of this account * according to the difference contained in the split @p s. * If the s.action() is MyMoneySplit::ActionSplitShares then * the balance will be adjusted accordingly. * * @param s const reference to MyMoneySplit object containing the * value to be added/subtracted to/from the balance * @param reverse add (false) or subtract (true) the shares contained in the split. * It also affects the balance for share splits in the opposite direction. */ void adjustBalance(const MyMoneySplit& s, bool reverse = false); /** * This method sets the balance of this account * according to the value provided by @p val. * * @param val const reference to MyMoneyMoney object containing the * value to be assigned to the balance */ void setBalance(const MyMoneyMoney& val); /** * This method sets the kvp's for online banking with this account * * @param values The container of kvp's needed when connecting to this account */ void setOnlineBankingSettings(const MyMoneyKeyValueContainer& values); /** * This method retrieves the kvp's for online banking with this account * * @return The container of kvp's needed when connecting to this account */ const MyMoneyKeyValueContainer& onlineBankingSettings() const; /** * This method sets the closed flag for the account. This is just * an informational flag for the application. It has no other influence * on the behaviour of the account object. The default for * new objects @p open. * * @param isClosed mark the account closed (@p true) or open (@p false). */ void setClosed(bool isClosed); /** * Return the closed flag for the account. * * @retval false account is marked open (the default for new accounts) * @retval true account is marked closed */ bool isClosed() const; /** * returns the applicable smallest fraction for this account * for the given security based on the account type. At the same * time, m_fraction is updated to the value returned. * * @param sec const reference to currency (security) * * @retval sec.smallestCashFraction() for account type Cash * @retval sec.smallestAccountFraction() for all other account types */ int fraction(const MyMoneySecurity& sec); /** * Same as the above method, but does not modify m_fraction. */ int fraction(const MyMoneySecurity& sec) const; /** * This method returns the stored value for the fraction of this * account or -1 if not initialized. It can be initialized by * calling fraction(const MyMoneySecurity& sec) once. * * @note Don't use this method outside of KMyMoney application context (eg. testcases). * Use the above method instead. */ int fraction() const; /** * This method returns @a true if the account type is * either Income or Expense * * @retval true account is of type income or expense * @retval false for all other account types * * @deprecated use isIncomeExpense() instead */ KMM_MYMONEY_DEPRECATED bool isCategory() const; /** * This method returns @a true if the account type is * either Income or Expense * * @retval true account is of type income or expense * @retval false for all other account types */ bool isIncomeExpense() const; /** * This method returns @a true if the account type is * either Asset or Liability * * @retval true account is of type asset or liability * @retval false for all other account types */ bool isAssetLiability() const; /** * This method returns @a true if the account type is * either AssetLoan or Loan * * @retval true account is of type Loan or AssetLoan * @retval false for all other account types */ bool isLoan() const; /** * This method returns @a true if the account type is * Stock * * @retval true account is of type Stock * @retval false for all other account types */ bool isInvest() const; /** * This method returns @a true if the account type is * Checkings, Savings or Cash * * @retval true account is of type Checkings, Savings or Cash * @retval false for all other account types */ bool isLiquidAsset() const; /** * This method returns true if a costcenter assignment is required for this account */ bool isCostCenterRequired() const; /** * This method allows to control if a cost center assignment is required * for this account. It is if @a required is @c true (the default). */ void setCostCenterRequired(bool required = true); /** * This method returns a name that has a brokerage suffix of * the current name. It only works on investment accounts and * returns the name for all other cases. */ QString brokerageName() const; /** * @param reconcileFlag if set to @a true a reconcile overlay will be * added to the pixmap returned * @param size is a hint for the size of the icon * @return a pixmap using DesktopIcon for the account type */ QPixmap accountPixmap(const bool reconcileFlag = false, const int size = 64) const; /** * This method is used to convert the internal representation of * an account type into a human readable format * * @param accountType numerical representation of the account type. * For possible values, see eMyMoney::Account::Type * @return QString representing the human readable form */ static QString accountTypeToString(const eMyMoney::Account::Type accountType); /** * keeps a history record of a reconciliation for this account on @a date * with @a amount. * * @return @p true in case entry was added, @p false otherwise * * @sa reconciliationHistory() */ bool addReconciliation(const QDate& date, const MyMoneyMoney& amount); /** * @return QMap with the reconciliation history for the account * * @sa addReconciliation() */ const QMap& reconciliationHistory(); QDataStream &operator<<(const MyMoneyAccount &); QDataStream &operator>>(MyMoneyAccount &); }; inline void swap(MyMoneyAccount& first, MyMoneyAccount& second) // krazy:exclude=inline { using std::swap; - swap(first.d_ptr, second.d_ptr); - swap(first.m_id, second.m_id); + swap(first.MyMoneyObject::d_ptr, second.MyMoneyObject::d_ptr); swap(first.MyMoneyKeyValueContainer::d_ptr, second.MyMoneyKeyValueContainer::d_ptr); } inline MyMoneyAccount::MyMoneyAccount(MyMoneyAccount && other) : MyMoneyAccount() // krazy:exclude=inline { swap(*this, other); } inline MyMoneyAccount & MyMoneyAccount::operator=(MyMoneyAccount other) // krazy:exclude=inline { swap(*this, other); return *this; } template< class type > QList< payeeIdentifierTyped > MyMoneyAccount::payeeIdentifiersByType() const { QList< payeeIdentifierTyped > typedList; return typedList; } template<> QList< payeeIdentifierTyped< ::payeeIdentifiers::ibanBic> > MyMoneyAccount::payeeIdentifiersByType() const; /** * Make it possible to hold @ref MyMoneyAccount objects, * @ref accountTypeE and @ref amountTypeE inside @ref QVariant objects. */ Q_DECLARE_METATYPE(MyMoneyAccount) Q_DECLARE_METATYPE(eMyMoney::Account::Type) #endif diff --git a/kmymoney/mymoney/mymoneyaccount_p.h b/kmymoney/mymoney/mymoneyaccount_p.h index 546bf1cca..d5c672f73 100644 --- a/kmymoney/mymoney/mymoneyaccount_p.h +++ b/kmymoney/mymoney/mymoneyaccount_p.h @@ -1,205 +1,206 @@ /*************************************************************************** mymoneyaccount.cpp ------------------- copyright : (C) 2000 by Michael Edwardes (C) 2002 by Thomas Baumgart (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 MYMONEYACCOUNT_P_H #define MYMONEYACCOUNT_P_H // ---------------------------------------------------------------------------- // QT Includes #include #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes +#include "mymoneyobject_p.h" #include "mymoneykeyvaluecontainer.h" #include "mymoneymoney.h" #include "mymoneyenums.h" using namespace eMyMoney; namespace eMyMoney { namespace Account { enum class Element { SubAccount, SubAccounts, OnlineBanking }; uint qHash(const Element key, uint seed) { return ::qHash(static_cast(key), seed); } enum class Attribute { ID = 0 , Name, Type, ParentAccount, LastReconciled, LastModified, Institution, Opened, Number, Description, Currency, OpeningBalance, IBAN, BIC, // insert new entries above this line LastAttribute }; uint qHash(const Attribute key, uint seed) { return ::qHash(static_cast(key), seed); } } } -class MyMoneyAccountPrivate { - +class MyMoneyAccountPrivate : public MyMoneyObjectPrivate +{ public: MyMoneyAccountPrivate() : m_accountType(Account::Type::Unknown), m_fraction(-1) { } static QString getElName(const Account::Element el) { static const QMap elNames = { {Account::Element::SubAccount, QStringLiteral("SUBACCOUNT")}, {Account::Element::SubAccounts, QStringLiteral("SUBACCOUNTS")}, {Account::Element::OnlineBanking, QStringLiteral("ONLINEBANKING")} }; return elNames[el]; } static QString getAttrName(const Account::Attribute attr) { static const QHash attrNames = { {Account::Attribute::ID, QStringLiteral("id")}, {Account::Attribute::Name, QStringLiteral("name")}, {Account::Attribute::Type, QStringLiteral("type")}, {Account::Attribute::ParentAccount, QStringLiteral("parentaccount")}, {Account::Attribute::LastReconciled, QStringLiteral("lastreconciled")}, {Account::Attribute::LastModified, QStringLiteral("lastmodified")}, {Account::Attribute::Institution, QStringLiteral("institution")}, {Account::Attribute::Opened, QStringLiteral("opened")}, {Account::Attribute::Number, QStringLiteral("number")}, {Account::Attribute::Type, QStringLiteral("type")}, {Account::Attribute::Description, QStringLiteral("description")}, {Account::Attribute::Currency, QStringLiteral("currency")}, {Account::Attribute::OpeningBalance, QStringLiteral("openingbalance")}, {Account::Attribute::IBAN, QStringLiteral("iban")}, {Account::Attribute::BIC, QStringLiteral("bic")}, }; return attrNames[attr]; } /** * This member variable identifies the type of account */ eMyMoney::Account::Type m_accountType; /** * This member variable keeps the ID of the MyMoneyInstitution object * that this object belongs to. */ QString m_institution; /** * This member variable keeps the name of the account * It is solely for documentation purposes and it's contents is not * used otherwise by the mymoney-engine. */ QString m_name; /** * This member variable keeps the account number at the institution * It is solely for documentation purposes and it's contents is not * used otherwise by the mymoney-engine. */ QString m_number; /** * This member variable is a description of the account. * It is solely for documentation purposes and it's contents is not * used otherwise by the mymoney-engine. */ QString m_description; /** * This member variable keeps the date when the account * was last modified. */ QDate m_lastModified; /** * This member variable keeps the date when the * account was created as an object in a MyMoneyFile */ QDate m_openingDate; /** * This member variable keeps the date of the last * reconciliation of this account */ QDate m_lastReconciliationDate; /** * This member holds the ID's of all sub-ordinate accounts */ QStringList m_accountList; /** * This member contains the ID of the parent account */ QString m_parentAccount; /** * This member contains the ID of the currency associated with this account */ QString m_currencyId; /** * This member holds the balance of all transactions stored in the journal * for this account. */ MyMoneyMoney m_balance; /** * This member variable keeps the set of kvp's needed to establish * online banking sessions to this account. */ MyMoneyKeyValueContainer m_onlineBankingSettings; /** * This member keeps the fraction for the account. It is filled by MyMoneyFile * when set to -1. See also @sa fraction(const MyMoneySecurity&). */ int m_fraction; /** * This member keeps the reconciliation history */ QMap m_reconciliationHistory; }; #endif diff --git a/kmymoney/mymoney/mymoneybudget.cpp b/kmymoney/mymoney/mymoneybudget.cpp index 7c000711b..7e5e6d669 100644 --- a/kmymoney/mymoney/mymoneybudget.cpp +++ b/kmymoney/mymoney/mymoneybudget.cpp @@ -1,604 +1,599 @@ /*************************************************************************** mymoneybudget.cpp ------------------- begin : Sun July 4 2004 copyright : (C) 2004-2005 by Ace Jones email : acejones@users.sourceforge.net (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. * * * ***************************************************************************/ #include "mymoneybudget.h" #include "mymoneybudget_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes class MyMoneyBudget::PeriodGroupPrivate { public: QDate m_start; MyMoneyMoney m_amount; }; MyMoneyBudget::PeriodGroup::PeriodGroup() : d_ptr(new PeriodGroupPrivate) { } MyMoneyBudget::PeriodGroup::PeriodGroup(const MyMoneyBudget::PeriodGroup & other) : d_ptr(new PeriodGroupPrivate(*other.d_func())) { } MyMoneyBudget::PeriodGroup::~PeriodGroup() { Q_D(PeriodGroup); delete d; } QDate MyMoneyBudget::PeriodGroup::startDate() const { Q_D(const PeriodGroup); return d->m_start; } void MyMoneyBudget::PeriodGroup::setStartDate(const QDate& start) { Q_D(PeriodGroup); d->m_start = start; } MyMoneyMoney MyMoneyBudget::PeriodGroup::amount() const { Q_D(const PeriodGroup); return d->m_amount; } void MyMoneyBudget::PeriodGroup::setAmount(const MyMoneyMoney& amount) { Q_D(PeriodGroup); d->m_amount = amount; } bool MyMoneyBudget::PeriodGroup::operator == (const PeriodGroup& right) const { Q_D(const PeriodGroup); auto d2 = static_cast(right.d_func()); return (d->m_start == d2->m_start && d->m_amount == d2->m_amount); } const QStringList MyMoneyBudget::AccountGroup::kBudgetLevelText = QString("none,monthly,monthbymonth,yearly,invalid").split(','); const int BUDGET_VERSION = 2; class MyMoneyBudget::AccountGroupPrivate { public: AccountGroupPrivate() : m_budgetlevel(AccountGroup::eBudgetLevel::eNone), m_budgetsubaccounts(false) { } QString m_id; AccountGroup::eBudgetLevel m_budgetlevel; bool m_budgetsubaccounts; QMap m_periods; }; MyMoneyBudget::AccountGroup::AccountGroup() : d_ptr(new AccountGroupPrivate) { } MyMoneyBudget::AccountGroup::AccountGroup(const MyMoneyBudget::AccountGroup& other) : d_ptr(new AccountGroupPrivate(*other.d_func())) { } MyMoneyBudget::AccountGroup::~AccountGroup() { Q_D(AccountGroup); delete d; } bool MyMoneyBudget::AccountGroup::isZero() const { Q_D(const AccountGroup); return (!d->m_budgetsubaccounts && d->m_budgetlevel == eMonthly && balance().isZero()); } void MyMoneyBudget::AccountGroup::convertToMonthly() { MyMoneyBudget::PeriodGroup period; Q_D(AccountGroup); switch (d->m_budgetlevel) { case eYearly: case eMonthByMonth: period = d->m_periods.first(); // make him monthly period.setAmount(balance() / MyMoneyMoney(12, 1)); clearPeriods(); addPeriod(period.startDate(), period); break; default: break; } d->m_budgetlevel = eMonthly; } void MyMoneyBudget::AccountGroup::convertToYearly() { MyMoneyBudget::PeriodGroup period; Q_D(AccountGroup); switch (d->m_budgetlevel) { case eMonthByMonth: case eMonthly: period = d->m_periods.first(); // make him monthly period.setAmount(totalBalance()); clearPeriods(); addPeriod(period.startDate(), period); break; default: break; } d->m_budgetlevel = eYearly; } void MyMoneyBudget::AccountGroup::convertToMonthByMonth() { MyMoneyBudget::PeriodGroup period; QDate date; Q_D(AccountGroup); switch (d->m_budgetlevel) { case eYearly: case eMonthly: period = d->m_periods.first(); period.setAmount(totalBalance() / MyMoneyMoney(12, 1)); clearPeriods(); date = period.startDate(); for (auto i = 0; i < 12; ++i) { addPeriod(date, period); date = date.addMonths(1); period.setStartDate(date); } break; default: break; } d->m_budgetlevel = eMonthByMonth; } QString MyMoneyBudget::AccountGroup::id() const { Q_D(const AccountGroup); return d->m_id; } void MyMoneyBudget::AccountGroup::setId(const QString& id) { Q_D(AccountGroup); d->m_id = id; } bool MyMoneyBudget::AccountGroup::budgetSubaccounts() const { Q_D(const AccountGroup); return d->m_budgetsubaccounts; } void MyMoneyBudget::AccountGroup::setBudgetSubaccounts(bool budgetsubaccounts) { Q_D(AccountGroup); d->m_budgetsubaccounts = budgetsubaccounts; } MyMoneyBudget::AccountGroup::eBudgetLevel MyMoneyBudget::AccountGroup::budgetLevel() const { Q_D(const AccountGroup); return d->m_budgetlevel; } void MyMoneyBudget::AccountGroup::setBudgetLevel(eBudgetLevel level) { Q_D(AccountGroup); d->m_budgetlevel = level; } MyMoneyBudget::PeriodGroup MyMoneyBudget::AccountGroup::period(const QDate& date) const { Q_D(const AccountGroup); return d->m_periods[date]; } void MyMoneyBudget::AccountGroup::addPeriod(const QDate& date, PeriodGroup& period) { Q_D(AccountGroup); d->m_periods[date] = period; } const QMap MyMoneyBudget::AccountGroup::getPeriods() const { Q_D(const AccountGroup); return d->m_periods; } void MyMoneyBudget::AccountGroup::clearPeriods() { Q_D(AccountGroup); d->m_periods.clear(); } MyMoneyMoney MyMoneyBudget::AccountGroup::balance() const { Q_D(const AccountGroup); MyMoneyMoney balance; foreach (const auto period, d->m_periods) balance += period.amount(); return balance; } MyMoneyMoney MyMoneyBudget::AccountGroup::totalBalance() const { Q_D(const AccountGroup); auto bal = balance(); switch (d->m_budgetlevel) { default: break; case eMonthly: bal = bal * 12; break; } return bal; } MyMoneyBudget::AccountGroup MyMoneyBudget::AccountGroup::operator += (const MyMoneyBudget::AccountGroup& right) { Q_D(AccountGroup); auto d2 = static_cast(right.d_func()); // in case the right side is empty, we're done if (d2->m_budgetlevel == eNone) return *this; MyMoneyBudget::AccountGroup r(right); auto d3 = static_cast(r.d_func()); // make both operands based on the same budget level if (d->m_budgetlevel != d3->m_budgetlevel) { if (d->m_budgetlevel == eMonthly) { // my budget is monthly if (d3->m_budgetlevel == eYearly) { // his is yearly r.convertToMonthly(); } else if (d3->m_budgetlevel == eMonthByMonth) { // his is month by month convertToMonthByMonth(); } } else if (d->m_budgetlevel == eYearly) { // my budget is yearly if (d3->m_budgetlevel == eMonthly) { // his is monthly r.convertToYearly(); } else if (d3->m_budgetlevel == eMonthByMonth) { // his is month by month convertToMonthByMonth(); } } else if (d->m_budgetlevel == eMonthByMonth) { // my budget is month by month r.convertToMonthByMonth(); } } QMap rPeriods = d3->m_periods; QMap::const_iterator it_pr; // in case the left side is empty, we add empty periods // so that both budgets are identical if (d->m_budgetlevel == eNone) { it_pr = rPeriods.constBegin(); QDate date = (*it_pr).startDate(); while (it_pr != rPeriods.constEnd()) { MyMoneyBudget::PeriodGroup period = *it_pr; period.setAmount(MyMoneyMoney()); addPeriod(date, period); date = date.addMonths(1); ++it_pr; } d->m_budgetlevel = d3->m_budgetlevel; } QMap periods = d->m_periods; QMap::const_iterator it_p; // now both budgets should be of the same type and we simply need // to iterate over the period list and add the values d->m_periods.clear(); it_p = periods.constBegin(); it_pr = rPeriods.constBegin(); QDate date = (*it_p).startDate(); while (it_p != periods.constEnd()) { MyMoneyBudget::PeriodGroup period = *it_p; if (it_pr != rPeriods.constEnd()) { period.setAmount(period.amount() + (*it_pr).amount()); ++it_pr; } addPeriod(date, period); date = date.addMonths(1); ++it_p; } return *this; } bool MyMoneyBudget::AccountGroup::operator == (const AccountGroup& right) const { Q_D(const AccountGroup); auto d2 = static_cast(right.d_func()); return (d->m_id == d2->m_id && d->m_budgetlevel == d2->m_budgetlevel && d->m_budgetsubaccounts == d2->m_budgetsubaccounts && d->m_periods.keys() == d2->m_periods.keys() && d->m_periods.values() == d2->m_periods.values()); } MyMoneyBudget::MyMoneyBudget() : - d_ptr(new MyMoneyBudgetPrivate) + MyMoneyObject(*new MyMoneyBudgetPrivate) { Q_D(MyMoneyBudget); d->m_name = QLatin1Literal("Unconfigured Budget"); } MyMoneyBudget::MyMoneyBudget(const QString& name) : - d_ptr(new MyMoneyBudgetPrivate) + MyMoneyObject(*new MyMoneyBudgetPrivate) { Q_D(MyMoneyBudget); d->m_name = name; } MyMoneyBudget::MyMoneyBudget(const QDomElement& node) : - MyMoneyObject(node), - d_ptr(new MyMoneyBudgetPrivate) + MyMoneyObject(*new MyMoneyBudgetPrivate, node) { if (!read(node)) clearId(); } MyMoneyBudget::MyMoneyBudget(const QString& id, const MyMoneyBudget& other) : - MyMoneyObject(id), - d_ptr(new MyMoneyBudgetPrivate(*other.d_func())) + MyMoneyObject(*new MyMoneyBudgetPrivate(*other.d_func()), id) { } MyMoneyBudget::MyMoneyBudget(const MyMoneyBudget& other) : - MyMoneyObject(other.id()), - d_ptr(new MyMoneyBudgetPrivate(*other.d_func())) + MyMoneyObject(*new MyMoneyBudgetPrivate(*other.d_func()), other.id()) { } MyMoneyBudget::~MyMoneyBudget() { - Q_D(MyMoneyBudget); - delete d; } bool MyMoneyBudget::operator == (const MyMoneyBudget& right) const { Q_D(const MyMoneyBudget); auto d2 = static_cast(right.d_func()); return (MyMoneyObject::operator==(right) && (d->m_accounts.count() == d2->m_accounts.count()) && (d->m_accounts.keys() == d2->m_accounts.keys()) && (d->m_accounts.values() == d2->m_accounts.values()) && (d->m_name == d2->m_name) && (d->m_start == d2->m_start)); } void MyMoneyBudget::write(QDomElement& e, QDomDocument *doc) const { - writeBaseXML(*doc, e); - Q_D(const MyMoneyBudget); + d->writeBaseXML(*doc, e); + e.setAttribute(d->getAttrName(Budget::Attribute::Name), d->m_name); e.setAttribute(d->getAttrName(Budget::Attribute::Start), d->m_start.toString(Qt::ISODate)); e.setAttribute(d->getAttrName(Budget::Attribute::Version), BUDGET_VERSION); QMap::const_iterator it; for (it = d->m_accounts.begin(); it != d->m_accounts.end(); ++it) { // only add the account if there is a budget entered // or it covers some sub accounts if (!(*it).balance().isZero() || (*it).budgetSubaccounts()) { QDomElement domAccount = doc->createElement(d->getElName(Budget::Element::Account)); domAccount.setAttribute(d->getAttrName(Budget::Attribute::ID), it.key()); domAccount.setAttribute(d->getAttrName(Budget::Attribute::BudgetLevel), AccountGroup::kBudgetLevelText[it.value().budgetLevel()]); domAccount.setAttribute(d->getAttrName(Budget::Attribute::BudgetSubAccounts), it.value().budgetSubaccounts()); const QMap periods = it.value().getPeriods(); QMap::const_iterator it_per; for (it_per = periods.begin(); it_per != periods.end(); ++it_per) { if (!(*it_per).amount().isZero()) { QDomElement domPeriod = doc->createElement(d->getElName(Budget::Element::Period)); domPeriod.setAttribute(d->getAttrName(Budget::Attribute::Amount), (*it_per).amount().toString()); domPeriod.setAttribute(d->getAttrName(Budget::Attribute::Start), (*it_per).startDate().toString(Qt::ISODate)); domAccount.appendChild(domPeriod); } } e.appendChild(domAccount); } } } bool MyMoneyBudget::read(const QDomElement& e) { // The goal of this reading method is 100% backward AND 100% forward // compatibility. Any Budget ever created with any version of KMyMoney // should be able to be loaded by this method (as long as it's one of the // Budget types supported in this version, of course) Q_D(MyMoneyBudget); auto result = false; if (d->getElName(Budget::Element::Budget) == e.tagName()) { result = true; d->m_name = e.attribute(d->getAttrName(Budget::Attribute::Name)); d->m_start = QDate::fromString(e.attribute(d->getAttrName(Budget::Attribute::Start)), Qt::ISODate); - m_id = e.attribute(d->getAttrName(Budget::Attribute::ID)); + d->m_id = e.attribute(d->getAttrName(Budget::Attribute::ID)); QDomNode child = e.firstChild(); while (!child.isNull() && child.isElement()) { QDomElement c = child.toElement(); AccountGroup account; if (d->getElName(Budget::Element::Account) == c.tagName()) { if (c.hasAttribute(d->getAttrName(Budget::Attribute::ID))) account.setId(c.attribute(d->getAttrName(Budget::Attribute::ID))); if (c.hasAttribute(d->getAttrName(Budget::Attribute::BudgetLevel))) { int i = AccountGroup::kBudgetLevelText.indexOf(c.attribute(d->getAttrName(Budget::Attribute::BudgetLevel))); if (i != -1) account.setBudgetLevel(static_cast(i)); } if (c.hasAttribute(d->getAttrName(Budget::Attribute::BudgetSubAccounts))) account.setBudgetSubaccounts(c.attribute(d->getAttrName(Budget::Attribute::BudgetSubAccounts)).toUInt()); } QDomNode period = c.firstChild(); while (!period.isNull() && period.isElement()) { QDomElement per = period.toElement(); PeriodGroup pGroup; if (d->getElName(Budget::Element::Period) == per.tagName() && per.hasAttribute(d->getAttrName(Budget::Attribute::Amount)) && per.hasAttribute(d->getAttrName(Budget::Attribute::Start))) { pGroup.setAmount(MyMoneyMoney(per.attribute(d->getAttrName(Budget::Attribute::Amount)))); pGroup.setStartDate(QDate::fromString(per.attribute(d->getAttrName(Budget::Attribute::Start)), Qt::ISODate)); account.addPeriod(pGroup.startDate(), pGroup); } period = period.nextSibling(); } d->m_accounts[account.id()] = account; child = child.nextSibling(); } } return result; } void MyMoneyBudget::writeXML(QDomDocument& document, QDomElement& parent) const { Q_D(const MyMoneyBudget); QDomElement el = document.createElement(d->getElName(Budget::Element::Budget)); write(el, &document); parent.appendChild(el); } bool MyMoneyBudget::hasReferenceTo(const QString& id) const { Q_D(const MyMoneyBudget); // return true if we have an assignment for this id return (d->m_accounts.contains(id)); } void MyMoneyBudget::removeReference(const QString& id) { Q_D(MyMoneyBudget); if (d->m_accounts.contains(id)) { d->m_accounts.remove(id); } } const MyMoneyBudget::AccountGroup& MyMoneyBudget::account(const QString& id) const { static AccountGroup empty; QMap::ConstIterator it; Q_D(const MyMoneyBudget); it = d->m_accounts.constFind(id); if (it != d->m_accounts.constEnd()) return it.value(); return empty; } void MyMoneyBudget::setAccount(const AccountGroup& account, const QString& id) { Q_D(MyMoneyBudget); if (account.isZero()) { d->m_accounts.remove(id); } else { // make sure we store a correct id AccountGroup acc(account); if (acc.id() != id) acc.setId(id); d->m_accounts[id] = acc; } } bool MyMoneyBudget::contains(const QString &id) const { Q_D(const MyMoneyBudget); return d->m_accounts.contains(id); } QList MyMoneyBudget::getaccounts() const { Q_D(const MyMoneyBudget); return d->m_accounts.values(); } QString MyMoneyBudget::name() const { Q_D(const MyMoneyBudget); return d->m_name; } void MyMoneyBudget::setName(const QString& name) { Q_D(MyMoneyBudget); d->m_name = name; } QDate MyMoneyBudget::budgetStart() const { Q_D(const MyMoneyBudget); return d->m_start; } void MyMoneyBudget::setBudgetStart(const QDate& start) { Q_D(MyMoneyBudget); auto oldDate = QDate(d->m_start.year(), d->m_start.month(), 1); d->m_start = QDate(start.year(), start.month(), 1); if (oldDate.isValid()) { int adjust = ((d->m_start.year() - oldDate.year()) * 12) + (d->m_start.month() - oldDate.month()); QMap::iterator it; for (it = d->m_accounts.begin(); it != d->m_accounts.end(); ++it) { const QMap periods = (*it).getPeriods(); QMap::const_iterator it_per; (*it).clearPeriods(); for (it_per = periods.begin(); it_per != periods.end(); ++it_per) { PeriodGroup pgroup = (*it_per); pgroup.setStartDate(pgroup.startDate().addMonths(adjust)); (*it).addPeriod(pgroup.startDate(), pgroup); } } } } diff --git a/kmymoney/mymoney/mymoneybudget.h b/kmymoney/mymoney/mymoneybudget.h index 3df273e89..c4044af4d 100644 --- a/kmymoney/mymoney/mymoneybudget.h +++ b/kmymoney/mymoney/mymoneybudget.h @@ -1,308 +1,306 @@ /*************************************************************************** mymoneybudget.h ------------------- begin : Sun Jan 22 2006 copyright : (C) 2006 by Darren Gould email : darren_gould@gmx.de (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 MYMONEYBUDGET_H #define MYMONEYBUDGET_H // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyobject.h" #include "kmm_mymoney_export.h" #include "mymoneyunittestable.h" class QString; class QDate; class QDomElement; class QDomDocument; class MyMoneyMoney; template class QList; template class QMap; /** * This class defines a Budget within the MyMoneyEngine. The Budget class * contains all the configuration parameters needed to run a Budget, plus * XML serialization. * * As noted above, this class only provides a Budget DEFINITION. The * generation and presentation of the Budget itself are left to higher * level classes. * * @author Darren Gould */ class MyMoneyBudgetPrivate; class KMM_MYMONEY_EXPORT MyMoneyBudget: public MyMoneyObject { Q_DECLARE_PRIVATE(MyMoneyBudget) - MyMoneyBudgetPrivate* d_ptr; KMM_MYMONEY_UNIT_TESTABLE public: MyMoneyBudget(); explicit MyMoneyBudget(const QString& name); /** * This constructor creates an object based on the data found in the * QDomElement referenced by @p node. If problems arise, the @p id of * the object is cleared (see MyMoneyObject::clearId()). */ explicit MyMoneyBudget(const QDomElement& node); /** * This constructor creates an object based on the data found in the * MyMoneyBudget budget object. */ MyMoneyBudget(const QString& id, const MyMoneyBudget& other); MyMoneyBudget(const MyMoneyBudget& other); MyMoneyBudget(MyMoneyBudget && other); MyMoneyBudget & operator=(MyMoneyBudget other); friend void swap(MyMoneyBudget& first, MyMoneyBudget& second); ~MyMoneyBudget(); /** * Helper class for MyMoneyBudget * * This is an abstraction of the PERIOD stored in the BUDGET/ACCOUNT tag in XML * * @author Darren Gould */ class PeriodGroupPrivate; class KMM_MYMONEY_EXPORT PeriodGroup { Q_DECLARE_PRIVATE(PeriodGroup) PeriodGroupPrivate* d_ptr; public: PeriodGroup(); PeriodGroup(const PeriodGroup & other); PeriodGroup(PeriodGroup && other); PeriodGroup & operator=(PeriodGroup other); friend void swap(PeriodGroup& first, PeriodGroup& second); ~PeriodGroup(); QDate startDate() const; void setStartDate(const QDate& start); MyMoneyMoney amount() const; void setAmount(const MyMoneyMoney& amount); bool operator == (const PeriodGroup &right) const; }; /** * Helper class for MyMoneyBudget * * This is an abstraction of the Account Data stored in the BUDGET tag in XML * * @author Darren Gould */ class AccountGroupPrivate; class KMM_MYMONEY_EXPORT AccountGroup { Q_DECLARE_PRIVATE(AccountGroup) AccountGroupPrivate* d_ptr; public: typedef enum { eNone = 0, eMonthly, eMonthByMonth, eYearly, eMax } eBudgetLevel; static const QStringList kBudgetLevelText; public: AccountGroup(); AccountGroup(const AccountGroup & other); AccountGroup(AccountGroup && other); AccountGroup & operator=(AccountGroup other); friend void swap(AccountGroup& first, AccountGroup& second); ~AccountGroup(); QString id() const; void setId(const QString& id); bool budgetSubaccounts() const; void setBudgetSubaccounts(bool budgetsubaccounts); eBudgetLevel budgetLevel() const; void setBudgetLevel(eBudgetLevel level); PeriodGroup period(const QDate& date) const; void addPeriod(const QDate& date, PeriodGroup& period); const QMap getPeriods() const; void clearPeriods(); MyMoneyMoney balance() const; MyMoneyMoney totalBalance() const; // This member adds the value of another account group // m_budgetlevel is adjusted to the larger one of both // m_budgetsubaccounts remains unaffected AccountGroup operator += (const AccountGroup& right); bool operator == (const AccountGroup &right) const; bool isZero() const; protected: void convertToMonthly(); void convertToYearly(); void convertToMonthByMonth(); }; /** * This operator tests for equality of two MyMoneyBudget objects */ bool operator == (const MyMoneyBudget &) const; QString name() const; void setName(const QString& name); QDate budgetStart() const; void setBudgetStart(const QDate& start); const AccountGroup & account(const QString &id) const; void setAccount(const AccountGroup& account, const QString &id); bool contains(const QString &id) const; QList getaccounts() const; /** * This method writes this Budget to the DOM element @p e, * within the DOM document @p doc. * * @param e The element which should be populated with info from this Budget * @param doc The document which we can use to create new sub-elements * if needed */ void write(QDomElement& e, QDomDocument *doc) const; /** * This method reads a Budget from the DOM element @p e, and * populates this Budget with the results. * * @param e The element from which the Budget should be read * * @return bool True if a Budget was successfully loaded from the * element @p e. If false is returned, the contents of this Budget * object are undefined. */ bool read(const QDomElement& e); /** * This method creates a QDomElement for the @p document * under the parent node @p parent. (This version overwrites the * MMObject base class.) * * @param document reference to QDomDocument * @param parent reference to QDomElement parent node */ void writeXML(QDomDocument& document, QDomElement& parent) const override; /** * This method checks if a reference to the given object exists. It returns, * a @p true if the object is referencing the one requested by the * parameter @p id and the balance() returned is zero. * If it does not, this method returns @p false. * * @param id id of the object to be checked for references * @retval true This object references object with id @p id. * @retval false This object does not reference the object with id @p id. */ bool hasReferenceTo(const QString& id) const override; /** * This member removes all references to object identified by @p id. Used * to remove objects which are about to be removed from the engine. */ void removeReference(const QString& id); }; inline void swap(MyMoneyBudget::PeriodGroup& first, MyMoneyBudget::PeriodGroup& second) // krazy:exclude=inline { using std::swap; swap(first.d_ptr, second.d_ptr); } inline MyMoneyBudget::PeriodGroup::PeriodGroup(MyMoneyBudget::PeriodGroup && other) : PeriodGroup() // krazy:exclude=inline { swap(*this, other); } inline MyMoneyBudget::PeriodGroup & MyMoneyBudget::PeriodGroup::operator=(MyMoneyBudget::PeriodGroup other) // krazy:exclude=inline { swap(*this, other); return *this; } inline void swap(MyMoneyBudget::AccountGroup& first, MyMoneyBudget::AccountGroup& second) // krazy:exclude=inline { using std::swap; swap(first.d_ptr, second.d_ptr); } inline MyMoneyBudget::AccountGroup::AccountGroup(MyMoneyBudget::AccountGroup && other) : AccountGroup() // krazy:exclude=inline { swap(*this, other); } inline MyMoneyBudget::AccountGroup & MyMoneyBudget::AccountGroup::operator=(MyMoneyBudget::AccountGroup other) // krazy:exclude=inline { swap(*this, other); return *this; } inline void swap(MyMoneyBudget& first, MyMoneyBudget& second) // krazy:exclude=inline { using std::swap; swap(first.d_ptr, second.d_ptr); - swap(first.m_id, second.m_id); } inline MyMoneyBudget::MyMoneyBudget(MyMoneyBudget && other) : MyMoneyBudget() // krazy:exclude=inline { swap(*this, other); } inline MyMoneyBudget & MyMoneyBudget::operator=(MyMoneyBudget other) // krazy:exclude=inline { swap(*this, other); return *this; } /** * Make it possible to hold @ref MyMoneyBudget objects inside @ref QVariant objects. */ Q_DECLARE_METATYPE(MyMoneyBudget) #endif // MYMONEYBudget_H diff --git a/kmymoney/mymoney/mymoneybudget_p.h b/kmymoney/mymoney/mymoneybudget_p.h index bb0d9f7b0..253490f99 100644 --- a/kmymoney/mymoney/mymoneybudget_p.h +++ b/kmymoney/mymoney/mymoneybudget_p.h @@ -1,103 +1,104 @@ /*************************************************************************** mymoneybudget.cpp ------------------- begin : Sun July 4 2004 copyright : (C) 2004-2005 by Ace Jones email : acejones@users.sourceforge.net (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 MYMONEYBUDGET_P_H #define MYMONEYBUDGET_P_H // ---------------------------------------------------------------------------- // QT Includes #include #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes +#include "mymoneyobject_p.h" #include "mymoneymoney.h" namespace Budget { enum class Element { Budget = 0, Account, Period }; uint qHash(const Element key, uint seed) { return ::qHash(static_cast(key), seed); } enum class Attribute { ID = 0, Name, Start, Version, BudgetLevel, BudgetSubAccounts, Amount, // insert new entries above this line LastAttribute }; uint qHash(const Attribute key, uint seed) { return ::qHash(static_cast(key), seed); } } -class MyMoneyBudgetPrivate { - +class MyMoneyBudgetPrivate : public MyMoneyObjectPrivate +{ public: static QString getElName(const Budget::Element el) { static const QMap elNames { {Budget::Element::Budget, "BUDGET"}, {Budget::Element::Account, "ACCOUNT"}, {Budget::Element::Period, "PERIOD"} }; return elNames[el]; } static QString getAttrName(const Budget::Attribute attr) { static const QHash attrNames { {Budget::Attribute::ID, QStringLiteral("id")}, {Budget::Attribute::Name, QStringLiteral("name")}, {Budget::Attribute::Start, QStringLiteral("start")}, {Budget::Attribute::Version, QStringLiteral("version")}, {Budget::Attribute::BudgetLevel, QStringLiteral("budgetlevel")}, {Budget::Attribute::BudgetSubAccounts, QStringLiteral("budgetsubaccounts")}, {Budget::Attribute::Amount, QStringLiteral("amount")} }; return attrNames[attr]; } /** * The user-assigned name of the Budget */ QString m_name; /** * The user-assigned year of the Budget */ QDate m_start; /** * Map the budgeted accounts * * Each account Id is stored against the AccountGroup information */ QMap m_accounts; }; #endif diff --git a/kmymoney/mymoney/mymoneycostcenter.cpp b/kmymoney/mymoney/mymoneycostcenter.cpp index 8b2b18753..3edb9ba01 100644 --- a/kmymoney/mymoney/mymoneycostcenter.cpp +++ b/kmymoney/mymoney/mymoneycostcenter.cpp @@ -1,143 +1,139 @@ /*************************************************************************** mymoneycostcenter.cpp ------------------- copyright : (C) 2015 Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneycostcenter.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // Project Includes +#include "mymoneyobject_p.h" #include "mymoneyexception.h" #include "mymoneystoragenames.h" using namespace MyMoneyStorageNodes; MyMoneyCostCenter MyMoneyCostCenter::null; -class MyMoneyCostCenterPrivate { - +class MyMoneyCostCenterPrivate : public MyMoneyObjectPrivate +{ public: QString m_name; }; MyMoneyCostCenter::MyMoneyCostCenter() : - d_ptr(new MyMoneyCostCenterPrivate) + MyMoneyObject(*new MyMoneyCostCenterPrivate) { } MyMoneyCostCenter::MyMoneyCostCenter(const QString& name) : - d_ptr(new MyMoneyCostCenterPrivate) + MyMoneyObject(*new MyMoneyCostCenterPrivate) { Q_D(MyMoneyCostCenter); d->m_name = name; } MyMoneyCostCenter::MyMoneyCostCenter(const QDomElement& node) : - MyMoneyObject(node), - d_ptr(new MyMoneyCostCenterPrivate) + MyMoneyObject(*new MyMoneyCostCenterPrivate, node) { if (nodeNames[nnCostCenter] != node.tagName()) throw MYMONEYEXCEPTION("Node was not COSTCENTER"); Q_D(MyMoneyCostCenter); d->m_name = node.attribute(getAttrName(Attribute::Name)); } MyMoneyCostCenter::MyMoneyCostCenter(const MyMoneyCostCenter& other) : - MyMoneyObject(other.id()), - d_ptr(new MyMoneyCostCenterPrivate(*other.d_func())) + MyMoneyObject(*new MyMoneyCostCenterPrivate(*other.d_func()), other.id()) { } MyMoneyCostCenter::MyMoneyCostCenter(const QString& id, const MyMoneyCostCenter& other) : - MyMoneyObject(id), - d_ptr(new MyMoneyCostCenterPrivate(*other.d_func())) + MyMoneyObject(*new MyMoneyCostCenterPrivate(*other.d_func()), id) { } MyMoneyCostCenter::~MyMoneyCostCenter() { - Q_D(MyMoneyCostCenter); - delete d; } bool MyMoneyCostCenter::operator == (const MyMoneyCostCenter& right) const { Q_D(const MyMoneyCostCenter); auto d2 = static_cast(right.d_func()); return (MyMoneyObject::operator==(right) && ((d->m_name.length() == 0 && d2->m_name.length() == 0) || (d->m_name == d2->m_name))); } bool MyMoneyCostCenter::operator < (const MyMoneyCostCenter& right) const { Q_D(const MyMoneyCostCenter); auto d2 = static_cast(right.d_func()); QCollator col; return col.compare(d->m_name, d2->m_name); } void MyMoneyCostCenter::writeXML(QDomDocument& document, QDomElement& parent) const { auto el = document.createElement(nodeNames[nnCostCenter]); - writeBaseXML(document, el); - Q_D(const MyMoneyCostCenter); + d->writeBaseXML(document, el); + el.setAttribute(getAttrName(Attribute::Name), d->m_name); parent.appendChild(el); } bool MyMoneyCostCenter::hasReferenceTo(const QString& /*id*/) const { return false; } QString MyMoneyCostCenter::name() const { Q_D(const MyMoneyCostCenter); return d->m_name; } void MyMoneyCostCenter::setName(const QString& val) { Q_D(MyMoneyCostCenter); d->m_name = val; } QString MyMoneyCostCenter::shortName() const { Q_D(const MyMoneyCostCenter); QRegExp shortNumberExp("^(\\d+)\\s.+"); if(shortNumberExp.exactMatch(d->m_name)) { return shortNumberExp.cap(1); } return d->m_name; } QString MyMoneyCostCenter::getAttrName(const Attribute attr) { static const QMap attrNames = { {Attribute::Name, QStringLiteral("name")}, }; return attrNames[attr]; } diff --git a/kmymoney/mymoney/mymoneycostcenter.h b/kmymoney/mymoney/mymoneycostcenter.h index 320b4bc41..2ef1600ba 100644 --- a/kmymoney/mymoney/mymoneycostcenter.h +++ b/kmymoney/mymoney/mymoneycostcenter.h @@ -1,134 +1,132 @@ /*************************************************************************** mymoneycostcenter.h ------------------- copyright : (C) 2015 by Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MYMONEYCOSTCENTER_H #define MYMONEYCOSTCENTER_H #include "kmm_mymoney_export.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyobject.h" /** * This class represents a tag within the MyMoney engine. */ class MyMoneyCostCenterPrivate; class KMM_MYMONEY_EXPORT MyMoneyCostCenter : public MyMoneyObject { Q_DECLARE_PRIVATE(MyMoneyCostCenter) - MyMoneyCostCenterPrivate* d_ptr; KMM_MYMONEY_UNIT_TESTABLE public: MyMoneyCostCenter(); explicit MyMoneyCostCenter(const QString& name); /** * This is the constructor for a tag that is described by a * QDomElement (e.g. from a file). * * @param el const reference to the QDomElement from which to * create the object */ explicit MyMoneyCostCenter(const QDomElement& el); MyMoneyCostCenter(const QString& id, const MyMoneyCostCenter& other); MyMoneyCostCenter(const MyMoneyCostCenter & other); MyMoneyCostCenter(MyMoneyCostCenter && other); MyMoneyCostCenter & operator=(MyMoneyCostCenter other); friend void swap(MyMoneyCostCenter& first, MyMoneyCostCenter& second); ~MyMoneyCostCenter(); QString name() const; void setName(const QString& val); /** * This member returns a possible number leading the name. If there * is no number infront of the name, then the full name will be returned * @sa name() */ QString shortName() const; // Equality operator bool operator == (const MyMoneyCostCenter &) const; bool operator <(const MyMoneyCostCenter& right) const; void writeXML(QDomDocument& document, QDomElement& parent) const override; /** * This method checks if a reference to the given object exists. It returns, * a @p true if the object is referencing the one requested by the * parameter @p id. If it does not, this method returns @p false. * * @param id id of the object to be checked for references * @retval true This object references object with id @p id. * @retval false This object does not reference the object with id @p id. */ bool hasReferenceTo(const QString& id) const override; static MyMoneyCostCenter null; private: enum class Attribute { Name }; static QString getAttrName(const Attribute attr); friend uint qHash(const Attribute, uint seed); }; inline uint qHash(const MyMoneyCostCenter::Attribute key, uint seed) { return ::qHash(static_cast(key), seed); } // krazy:exclude=inline inline void swap(MyMoneyCostCenter& first, MyMoneyCostCenter& second) // krazy:exclude=inline { using std::swap; swap(first.d_ptr, second.d_ptr); - swap(first.m_id, second.m_id); } inline MyMoneyCostCenter::MyMoneyCostCenter(MyMoneyCostCenter && other) : MyMoneyCostCenter() // krazy:exclude=inline { swap(*this, other); } inline MyMoneyCostCenter & MyMoneyCostCenter::operator=(MyMoneyCostCenter other) // krazy:exclude=inline { swap(*this, other); return *this; } //inline bool operator==(const MyMoneyCostCenter& lhs, const QString& rhs) //{ // return lhs.id() == rhs; //} /** * Make it possible to hold @ref MyMoneyCostCenter objects inside @ref QVariant objects. */ Q_DECLARE_METATYPE(MyMoneyCostCenter) #endif diff --git a/kmymoney/mymoney/mymoneyinstitution.cpp b/kmymoney/mymoney/mymoneyinstitution.cpp index 8b13f0182..82f6caf80 100644 --- a/kmymoney/mymoney/mymoneyinstitution.cpp +++ b/kmymoney/mymoney/mymoneyinstitution.cpp @@ -1,331 +1,325 @@ /*************************************************************************** mymoneyinstitution.cpp ------------------- copyright : (C) 2001 by Michael Edwardes 2002-2005 by Thomas Baumgart (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. * * * ***************************************************************************/ #include "mymoneyinstitution.h" #include "mymoneyinstitution_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "icons.h" #include #include "mymoneystoragenames.h" using namespace MyMoneyStorageNodes; using namespace Icons; MyMoneyInstitution::MyMoneyInstitution() : - MyMoneyObject(), - MyMoneyKeyValueContainer(), - d_ptr(new MyMoneyInstitutionPrivate) + MyMoneyObject(*new MyMoneyInstitutionPrivate), + MyMoneyKeyValueContainer() { } MyMoneyInstitution::MyMoneyInstitution(const QString& name, const QString& town, const QString& street, const QString& postcode, const QString& telephone, const QString& manager, const QString& sortcode) : - MyMoneyKeyValueContainer(), - d_ptr(new MyMoneyInstitutionPrivate) + MyMoneyObject(*new MyMoneyInstitutionPrivate), + MyMoneyKeyValueContainer() { Q_D(MyMoneyInstitution); clearId(); d->m_name = name; d->m_town = town; d->m_street = street; d->m_postcode = postcode; d->m_telephone = telephone; d->m_manager = manager; d->m_sortcode = sortcode; } MyMoneyInstitution::MyMoneyInstitution(const QDomElement& node) : - MyMoneyObject(node), - MyMoneyKeyValueContainer(node.elementsByTagName(nodeNames[nnKeyValuePairs]).item(0).toElement()), - d_ptr(new MyMoneyInstitutionPrivate) + MyMoneyObject(*new MyMoneyInstitutionPrivate, node), + MyMoneyKeyValueContainer(node.elementsByTagName(nodeNames[nnKeyValuePairs]).item(0).toElement()) { if (nodeNames[nnInstitution] != node.tagName()) throw MYMONEYEXCEPTION("Node was not INSTITUTION"); Q_D(MyMoneyInstitution); d->m_sortcode = node.attribute(d->getAttrName(Institution::Attribute::SortCode)); d->m_name = node.attribute(d->getAttrName(Institution::Attribute::Name)); d->m_manager = node.attribute(d->getAttrName(Institution::Attribute::Manager)); QDomNodeList nodeList = node.elementsByTagName(d->getElName(Institution::Element::Address)); if (nodeList.count() == 0) { QString msg = QString("No ADDRESS in institution %1").arg(d->m_name); throw MYMONEYEXCEPTION(msg); } QDomElement addrNode = nodeList.item(0).toElement(); d->m_street = addrNode.attribute(d->getAttrName(Institution::Attribute::Street)); d->m_town = addrNode.attribute(d->getAttrName(Institution::Attribute::City)); d->m_postcode = addrNode.attribute(d->getAttrName(Institution::Attribute::Zip)); d->m_telephone = addrNode.attribute(d->getAttrName(Institution::Attribute::Telephone)); d->m_accountList.clear(); nodeList = node.elementsByTagName(d->getElName(Institution::Element::AccountIDS)); if (nodeList.count() > 0) { nodeList = nodeList.item(0).toElement().elementsByTagName(d->getElName(Institution::Element::AccountID)); for (int i = 0; i < nodeList.count(); ++i) { d->m_accountList << nodeList.item(i).toElement().attribute(d->getAttrName(Institution::Attribute::ID)); } } } MyMoneyInstitution::MyMoneyInstitution(const MyMoneyInstitution& other) : - MyMoneyObject(other.id()), - MyMoneyKeyValueContainer(other), - d_ptr(new MyMoneyInstitutionPrivate(*other.d_func())) + MyMoneyObject(*new MyMoneyInstitutionPrivate(*other.d_func()), other.id()), + MyMoneyKeyValueContainer(other) { } MyMoneyInstitution::MyMoneyInstitution(const QString& id, const MyMoneyInstitution& other) : - MyMoneyObject(id), - MyMoneyKeyValueContainer(other), - d_ptr(new MyMoneyInstitutionPrivate(*other.d_func())) + MyMoneyObject(*new MyMoneyInstitutionPrivate(*other.d_func()), id), + MyMoneyKeyValueContainer(other) { } MyMoneyInstitution::~MyMoneyInstitution() { - Q_D(MyMoneyInstitution); - delete d; } QString MyMoneyInstitution::manager() const { Q_D(const MyMoneyInstitution); return d->m_manager; } void MyMoneyInstitution::setManager(const QString& manager) { Q_D(MyMoneyInstitution); d->m_manager = manager; } QString MyMoneyInstitution::name() const { Q_D(const MyMoneyInstitution); return d->m_name; } void MyMoneyInstitution::setName(const QString& name) { Q_D(MyMoneyInstitution); d->m_name = name; } QString MyMoneyInstitution::postcode() const { Q_D(const MyMoneyInstitution); return d->m_postcode; } void MyMoneyInstitution::setPostcode(const QString& code) { Q_D(MyMoneyInstitution); d->m_postcode = code; } QString MyMoneyInstitution::street() const { Q_D(const MyMoneyInstitution); return d->m_street; } void MyMoneyInstitution::setStreet(const QString& street) { Q_D(MyMoneyInstitution); d->m_street = street; } QString MyMoneyInstitution::telephone() const { Q_D(const MyMoneyInstitution); return d->m_telephone; } void MyMoneyInstitution::setTelephone(const QString& tel) { Q_D(MyMoneyInstitution); d->m_telephone = tel; } QString MyMoneyInstitution::town() const { Q_D(const MyMoneyInstitution); return d->m_town; } void MyMoneyInstitution::setTown(const QString& town) { Q_D(MyMoneyInstitution); d->m_town = town; } QString MyMoneyInstitution::city() const { return town(); } void MyMoneyInstitution::setCity(const QString& town) { setTown(town); } QString MyMoneyInstitution::sortcode() const { Q_D(const MyMoneyInstitution); return d->m_sortcode; } void MyMoneyInstitution::setSortcode(const QString& code) { Q_D(MyMoneyInstitution); d->m_sortcode = code; } void MyMoneyInstitution::addAccountId(const QString& account) { Q_D(MyMoneyInstitution); // only add this account if it is not yet presently in the list if (d->m_accountList.contains(account) == 0) d->m_accountList.append(account); } QString MyMoneyInstitution::removeAccountId(const QString& account) { Q_D(MyMoneyInstitution); QString rc; auto pos = d->m_accountList.indexOf(account); if (pos != -1) { d->m_accountList.removeAt(pos); rc = account; } return rc; } QStringList MyMoneyInstitution::accountList() const { Q_D(const MyMoneyInstitution); return d->m_accountList; } /** * This method returns the number of accounts known to * this institution * @return number of accounts */ unsigned int MyMoneyInstitution::accountCount() const { Q_D(const MyMoneyInstitution); return d->m_accountList.count(); } bool MyMoneyInstitution::operator < (const MyMoneyInstitution& right) const { Q_D(const MyMoneyInstitution); auto d2 = static_cast(right.d_func()); return d->m_name < d2->m_name; } bool MyMoneyInstitution::operator == (const MyMoneyInstitution& right) const { Q_D(const MyMoneyInstitution); auto d2 = static_cast(right.d_func()); if (MyMoneyObject::operator==(right) && ((d->m_name.length() == 0 && d2->m_name.length() == 0) || (d->m_name == d2->m_name)) && ((d->m_town.length() == 0 && d2->m_town.length() == 0) || (d->m_town == d2->m_town)) && ((d->m_street.length() == 0 && d2->m_street.length() == 0) || (d->m_street == d2->m_street)) && ((d->m_postcode.length() == 0 && d2->m_postcode.length() == 0) || (d->m_postcode == d2->m_postcode)) && ((d->m_telephone.length() == 0 && d2->m_telephone.length() == 0) || (d->m_telephone == d2->m_telephone)) && ((d->m_sortcode.length() == 0 && d2->m_sortcode.length() == 0) || (d->m_sortcode == d2->m_sortcode)) && ((d->m_manager.length() == 0 && d2->m_manager.length() == 0) || (d->m_manager == d2->m_manager)) && (d->m_accountList == d2->m_accountList)) { return true; } else return false; } void MyMoneyInstitution::writeXML(QDomDocument& document, QDomElement& parent) const { + Q_D(const MyMoneyInstitution); auto el = document.createElement(nodeNames[nnInstitution]); - writeBaseXML(document, el); + d->writeBaseXML(document, el); - Q_D(const MyMoneyInstitution); el.setAttribute(d->getAttrName(Institution::Attribute::Name), d->m_name); el.setAttribute(d->getAttrName(Institution::Attribute::Manager), d->m_manager); el.setAttribute(d->getAttrName(Institution::Attribute::SortCode), d->m_sortcode); auto address = document.createElement(d->getElName(Institution::Element::Address)); address.setAttribute(d->getAttrName(Institution::Attribute::Street), d->m_street); address.setAttribute(d->getAttrName(Institution::Attribute::City), d->m_town); address.setAttribute(d->getAttrName(Institution::Attribute::Zip), d->m_postcode); address.setAttribute(d->getAttrName(Institution::Attribute::Telephone), d->m_telephone); el.appendChild(address); auto accounts = document.createElement(d->getElName(Institution::Element::AccountIDS)); foreach (const auto accountID, accountList()) { auto temp = document.createElement(d->getElName(Institution::Element::AccountID)); temp.setAttribute(d->getAttrName(Institution::Attribute::ID), accountID); accounts.appendChild(temp); } el.appendChild(accounts); //Add in Key-Value Pairs for institutions. MyMoneyKeyValueContainer::writeXML(document, el); parent.appendChild(el); } bool MyMoneyInstitution::hasReferenceTo(const QString& /* id */) const { return false; } QPixmap MyMoneyInstitution::pixmap(const int size) const { QPixmap pxIcon; auto kyIcon = g_Icons.value(Icon::ViewInstitutions) + QString::number(size); if (!QPixmapCache::find(kyIcon, pxIcon)) { pxIcon = QIcon::fromTheme(g_Icons.value(Icon::ViewInstitutions)).pixmap(size); QPixmapCache::insert(kyIcon, pxIcon); } return pxIcon; } diff --git a/kmymoney/mymoney/mymoneyinstitution.h b/kmymoney/mymoney/mymoneyinstitution.h index ed9dd1377..7eb0f286a 100644 --- a/kmymoney/mymoney/mymoneyinstitution.h +++ b/kmymoney/mymoney/mymoneyinstitution.h @@ -1,197 +1,195 @@ /*************************************************************************** mymoneyinstitution.h ------------------- copyright : (C) 2002-2005 by Thomas Baumgart (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 MYMONEYINSTITUTION_H #define MYMONEYINSTITUTION_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyobject.h" #include "mymoneykeyvaluecontainer.h" #include "kmm_mymoney_export.h" class QString; class QStringList; class QPixmap; /** * This class represents a Bank contained within a MyMoneyFile object * * @author Thomas Baumgart * @author Łukasz Wojniłowicz */ class MyMoneyInstitutionPrivate; class KMM_MYMONEY_EXPORT MyMoneyInstitution : public MyMoneyObject, public MyMoneyKeyValueContainer { - Q_DECLARE_PRIVATE(MyMoneyInstitution) - MyMoneyInstitutionPrivate* d_ptr; + Q_DECLARE_PRIVATE_D(MyMoneyObject::d_ptr, MyMoneyInstitution) KMM_MYMONEY_UNIT_TESTABLE public: /** * This is the constructor for a new empty institution description */ MyMoneyInstitution(); /** * This is the constructor used by an application to fill the * values required for a new institution. This object should then be * passed to @see MyMoneyFile::addInstitution */ explicit MyMoneyInstitution(const QString& name, const QString& city, const QString& street, const QString& postcode, const QString& telephone, const QString& manager, const QString& sortCode); MyMoneyInstitution(const MyMoneyInstitution & other); MyMoneyInstitution(MyMoneyInstitution && other); MyMoneyInstitution & operator=(MyMoneyInstitution other); friend void swap(MyMoneyInstitution& first, MyMoneyInstitution& second); /** * This is the destructor for any MyMoneyInstitution object */ ~MyMoneyInstitution(); /** * This is the constructor for a new institution known to the current file * * @param id id assigned to the new institution object * @param right institution definition */ MyMoneyInstitution(const QString& id, const MyMoneyInstitution& other); /** * This is the constructor for an institution that is described by a * QDomElement (e.g. from a file). * * @param el const reference to the QDomElement from which to * create the object */ explicit MyMoneyInstitution(const QDomElement& el); QString manager() const; void setManager(const QString& manager); QString name() const; void setName(const QString& name); QString postcode() const; void setPostcode(const QString& code); QString street() const; void setStreet(const QString& street); QString telephone() const; void setTelephone(const QString& tel); QString town() const; void setTown(const QString& town); QString city() const; void setCity(const QString& town); QString sortcode() const; void setSortcode(const QString& code); /** * This method adds the id of an account to the account list of * this institution It is verified, that the account is only * mentioned once. * * @param account id of the account to be added */ void addAccountId(const QString& account); /** * This method deletes the id of an account from the account list * of this institution * * @param account id of the account to be deleted * @return id of account deleted, otherwise empty string */ QString removeAccountId(const QString& account); /** * This method is used to return the set of accounts known to * this institution * return QStringList of account ids */ QStringList accountList() const; /** * This method returns the number of accounts known to * this institution * @return number of accounts */ unsigned int accountCount() const; bool operator == (const MyMoneyInstitution&) const; bool operator < (const MyMoneyInstitution& right) const; void writeXML(QDomDocument& document, QDomElement& parent) const override; /** * This method checks if a reference to the given object exists. It returns, * a @p true if the object is referencing the one requested by the * parameter @p id. If it does not, this method returns @p false. * * @param id id of the object to be checked for references * @retval true This object references object with id @p id. * @retval false This object does not reference the object with id @p id. */ bool hasReferenceTo(const QString& id) const override; QPixmap pixmap(const int size = 64) const; }; inline void swap(MyMoneyInstitution& first, MyMoneyInstitution& second) // krazy:exclude=inline { using std::swap; - swap(first.d_ptr, second.d_ptr); - swap(first.m_id, second.m_id); + swap(first.MyMoneyObject::d_ptr, second.MyMoneyObject::d_ptr); swap(first.MyMoneyKeyValueContainer::d_ptr, second.MyMoneyKeyValueContainer::d_ptr); } inline MyMoneyInstitution::MyMoneyInstitution(MyMoneyInstitution && other) : MyMoneyInstitution() // krazy:exclude=inline { swap(*this, other); } inline MyMoneyInstitution & MyMoneyInstitution::operator=(MyMoneyInstitution other) // krazy:exclude=inline { swap(*this, other); return *this; } /** * Make it possible to hold @ref MyMoneyInstitution objects inside @ref QVariant objects. */ Q_DECLARE_METATYPE(MyMoneyInstitution) #endif diff --git a/kmymoney/mymoney/mymoneyinstitution_p.h b/kmymoney/mymoney/mymoneyinstitution_p.h index fa2d8e53e..86e0c2064 100644 --- a/kmymoney/mymoney/mymoneyinstitution_p.h +++ b/kmymoney/mymoney/mymoneyinstitution_p.h @@ -1,132 +1,134 @@ /*************************************************************************** mymoneyinstitution.cpp ------------------- copyright : (C) 2001 by Michael Edwardes 2002-2005 by Thomas Baumgart (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 MYMONEYINSTITUTION_P_H #define MYMONEYINSTITUTION_P_H #include "mymoneyinstitution.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes +#include "mymoneyobject_p.h" + namespace Institution { enum class Element { AccountID, AccountIDS, Address }; uint qHash(const Element key, uint seed) { return ::qHash(static_cast(key), seed); } enum class Attribute { ID = 0, Name, Manager, SortCode, Street, City, Zip, Telephone, // insert new entries above this line LastAttribute }; uint qHash(const Attribute key, uint seed) { return ::qHash(static_cast(key), seed); } } -class MyMoneyInstitutionPrivate { - +class MyMoneyInstitutionPrivate : public MyMoneyObjectPrivate +{ public: static QString getElName(const Institution::Element el) { - static const QMap elNames = { + static const QMap elNames { {Institution::Element::AccountID, QStringLiteral("ACCOUNTID")}, {Institution::Element::AccountIDS, QStringLiteral("ACCOUNTIDS")}, {Institution::Element::Address, QStringLiteral("ADDRESS")} }; return elNames[el]; } static QString getAttrName(const Institution::Attribute attr) { - static const QHash attrNames = { + static const QHash attrNames { {Institution::Attribute::ID, QStringLiteral("id")}, {Institution::Attribute::Name, QStringLiteral("name")}, {Institution::Attribute::Manager, QStringLiteral("manager")}, {Institution::Attribute::SortCode, QStringLiteral("sortcode")}, {Institution::Attribute::Street, QStringLiteral("street")}, {Institution::Attribute::City, QStringLiteral("city")}, {Institution::Attribute::Zip, QStringLiteral("zip")}, {Institution::Attribute::Telephone, QStringLiteral("telephone")} }; return attrNames[attr]; } /** * This member variable keeps the name of the institution */ QString m_name; /** * This member variable keeps the city of the institution */ QString m_town; /** * This member variable keeps the street of the institution */ QString m_street; /** * This member variable keeps the zip-code of the institution */ QString m_postcode; /** * This member variable keeps the telephone number of the institution */ QString m_telephone; /** * This member variable keeps the name of the representative of * the institution */ QString m_manager; /** * This member variable keeps the sort code of the institution. * FIXME: I have no idea * what it is good for. I keep it because it was in the old engine. */ QString m_sortcode; /** * This member variable keeps the sorted list of the account ids * available at this institution */ QStringList m_accountList; }; #endif diff --git a/kmymoney/mymoney/mymoneyobject.cpp b/kmymoney/mymoney/mymoneyobject.cpp index 61f579296..8e9c34f69 100644 --- a/kmymoney/mymoney/mymoneyobject.cpp +++ b/kmymoney/mymoney/mymoneyobject.cpp @@ -1,82 +1,95 @@ /*************************************************************************** mymoneyobject.cpp ------------------- copyright : (C) 2005 by Thomas Baumagrt email : ipwizard@users.sourceforge.net + (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. * * * ***************************************************************************/ #include "mymoneyobject.h" +#include "mymoneyobject_p.h" // ---------------------------------------------------------------------------- // QT Includes -#include -#include - // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyexception.h" -const QString MyMoneyObject::m_emptyId; - -MyMoneyObject::MyMoneyObject(const QString& id) +MyMoneyObject::MyMoneyObject(const QString& id) : + d_ptr(new MyMoneyObjectPrivate) { - m_id = id; + Q_D(MyMoneyObject); + d->m_id = id; } -MyMoneyObject::MyMoneyObject(const QDomElement& el, const bool forceId) +MyMoneyObject::MyMoneyObject(const QDomElement& node, bool forceId) { - m_id = el.attribute("id"); - if (m_id.length() == 0 && forceId) + Q_D(MyMoneyObject); + d->m_id = node.attribute(QStringLiteral("id")); + if (d->m_id.length() == 0 && forceId) throw MYMONEYEXCEPTION("Node has no ID"); } -MyMoneyObject::MyMoneyObject() +MyMoneyObject::MyMoneyObject() : + d_ptr(new MyMoneyObjectPrivate) { } -MyMoneyObject::~MyMoneyObject() +MyMoneyObject::MyMoneyObject(MyMoneyObjectPrivate &dd) : + d_ptr(&dd) { } -QString MyMoneyObject::id() const +MyMoneyObject::MyMoneyObject(MyMoneyObjectPrivate &dd, + const QString& id) : + d_ptr(&dd) { - return m_id; + Q_D(MyMoneyObject); + d->m_id = id; } -void MyMoneyObject::setId(const QString& id) +MyMoneyObject::MyMoneyObject(MyMoneyObjectPrivate &dd, + const QDomElement& node, + bool forceId) : + d_ptr(&dd) { - m_id = id; + Q_D(MyMoneyObject); + d->m_id = node.attribute(QStringLiteral("id")); + if (d->m_id.length() == 0 && forceId) + throw MYMONEYEXCEPTION("Node has no ID"); } -bool MyMoneyObject::operator == (const MyMoneyObject& right) const +MyMoneyObject::~MyMoneyObject() { - return m_id == right.m_id; + Q_D(MyMoneyObject); + delete d; } -void MyMoneyObject::clearId() +QString MyMoneyObject::id() const { - m_id.clear(); + Q_D(const MyMoneyObject); + return d->m_id; } -const QString& MyMoneyObject::emptyId() +bool MyMoneyObject::operator == (const MyMoneyObject& right) const { - return m_emptyId; + Q_D(const MyMoneyObject); + return d->m_id == right.d_func()->m_id; } -void MyMoneyObject::writeBaseXML(QDomDocument& document, QDomElement& el) const +void MyMoneyObject::clearId() { - Q_UNUSED(document); - - el.setAttribute(QStringLiteral("id"), m_id); + Q_D(MyMoneyObject); + d->m_id.clear(); } diff --git a/kmymoney/mymoney/mymoneyobject.h b/kmymoney/mymoney/mymoneyobject.h index a62826978..d8bb5b19d 100644 --- a/kmymoney/mymoney/mymoneyobject.h +++ b/kmymoney/mymoney/mymoneyobject.h @@ -1,128 +1,129 @@ /*************************************************************************** mymoneyobject.h ------------------- copyright : (C) 2005 by Thomas Baumgart email : ipwizard@users.sourceforge.net + (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 MYMONEYOBJECT_H #define MYMONEYOBJECT_H // ---------------------------------------------------------------------------- // QT Includes -#include -class QDomDocument; -class QDomElement; +#include // ---------------------------------------------------------------------------- // Project Includes #include "kmm_mymoney_export.h" #include "mymoneyunittestable.h" +class QString; +class QDomDocument; +class QDomElement; + /** * @author Thomas Baumgart */ /** * This class represents the base class of all MyMoney objects. */ +class MyMoneyObjectPrivate; class KMM_MYMONEY_EXPORT MyMoneyObject { + Q_DECLARE_PRIVATE(MyMoneyObject) + KMM_MYMONEY_UNIT_TESTABLE -public: - /** - * This is the constructor for the MyMoneyObject object - */ - MyMoneyObject(); + public: + /** + * This is the constructor for the MyMoneyObject object + */ + MyMoneyObject(); /** * This is the destructor for the MyMoneyObject object */ virtual ~MyMoneyObject(); /** * This method retrieves the id of the object * * @return ID of object */ QString id() const; /** * This method clears the id of the object */ void clearId(); /** * This method must be provided by all derived objects. It returns, * a @p true if the object is referencing the one requested by the * parameter @p id. If it does not, this method returns @p false. * * @param id id of the object to be checked for references * @retval true This object references object with id @p id. * @retval false This object does not reference the object with id @p id. */ virtual bool hasReferenceTo(const QString& id) const = 0; /** * This method creates a QDomElement for the @p document * under the parent node @p parent. * * @param document reference to QDomDocument * @param parent reference to QDomElement parent node */ virtual void writeXML(QDomDocument& document, QDomElement& parent) const = 0; bool operator == (const MyMoneyObject& right) const; - static const QString& emptyId(); - protected: + MyMoneyObjectPrivate * d_ptr; + MyMoneyObject(MyMoneyObjectPrivate &dd); + MyMoneyObject(MyMoneyObjectPrivate &dd, + const QString& id); + MyMoneyObject(MyMoneyObjectPrivate &dd, + const QDomElement& node, + bool forceId = true); + /** * This contructor assigns the id to the MyMoneyObject * * @param id ID of object */ MyMoneyObject(const QString& id); /** * This contructor reads the id from the @p id attribute of the * QDomElement. * * @param node const reference to the QDomElement from which to * obtain the id of the object * @param forceId flag to be able to suppress enforcement of an id * defaults to true which requires the node to have an * attribute with name @p id. If it does not contain such * an attribute, an exception will be thrown. If @p forceId * is false, no check for an id is performed. This will be * used by objects, which are stored w/o id (eg. splits, * transactions within schedules) */ - MyMoneyObject(const QDomElement& node, const bool forceId = true); - - void setId(const QString& id); - - /** - * This method writes out the members contained in this object. - */ - void writeBaseXML(QDomDocument& document, QDomElement& el) const; - -protected: - QString m_id; - static const QString m_emptyId; + explicit MyMoneyObject(const QDomElement& node, bool forceId = true); }; #endif diff --git a/kmymoney/mymoney/mymoneyobject.cpp b/kmymoney/mymoney/mymoneyobject_p.h similarity index 55% copy from kmymoney/mymoney/mymoneyobject.cpp copy to kmymoney/mymoney/mymoneyobject_p.h index 61f579296..d3fe503d0 100644 --- a/kmymoney/mymoney/mymoneyobject.cpp +++ b/kmymoney/mymoney/mymoneyobject_p.h @@ -1,82 +1,59 @@ /*************************************************************************** - mymoneyobject.cpp + mymoneyobject_p.h ------------------- copyright : (C) 2005 by Thomas Baumagrt email : ipwizard@users.sourceforge.net + (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. * * * ***************************************************************************/ -#include "mymoneyobject.h" +#ifndef MYMONEYOBJECT_P_H +#define MYMONEYOBJECT_P_H // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // Project Includes -#include "mymoneyexception.h" - -const QString MyMoneyObject::m_emptyId; - -MyMoneyObject::MyMoneyObject(const QString& id) -{ - m_id = id; -} - -MyMoneyObject::MyMoneyObject(const QDomElement& el, const bool forceId) +class MyMoneyObjectPrivate { - m_id = el.attribute("id"); - if (m_id.length() == 0 && forceId) - throw MYMONEYEXCEPTION("Node has no ID"); -} +public: + MyMoneyObjectPrivate() + { + } -MyMoneyObject::MyMoneyObject() -{ -} + virtual ~MyMoneyObjectPrivate() + { + } -MyMoneyObject::~MyMoneyObject() -{ -} + void setId(const QString& id) + { + m_id = id; + } -QString MyMoneyObject::id() const -{ - return m_id; -} + /** + * This method writes out the members contained in this object. + */ + void writeBaseXML(QDomDocument& document, QDomElement& el) const + { + Q_UNUSED(document); -void MyMoneyObject::setId(const QString& id) -{ - m_id = id; -} + el.setAttribute(QStringLiteral("id"), m_id); + } -bool MyMoneyObject::operator == (const MyMoneyObject& right) const -{ - return m_id == right.m_id; -} - -void MyMoneyObject::clearId() -{ - m_id.clear(); -} - -const QString& MyMoneyObject::emptyId() -{ - return m_emptyId; -} - -void MyMoneyObject::writeBaseXML(QDomDocument& document, QDomElement& el) const -{ - Q_UNUSED(document); + QString m_id; +}; - el.setAttribute(QStringLiteral("id"), m_id); -} +#endif diff --git a/kmymoney/mymoney/mymoneypayee.cpp b/kmymoney/mymoney/mymoneypayee.cpp index c70ffd735..678c3f44a 100644 --- a/kmymoney/mymoney/mymoneypayee.cpp +++ b/kmymoney/mymoney/mymoneypayee.cpp @@ -1,400 +1,395 @@ /*************************************************************************** mymoneypayee.cpp ------------------- copyright : (C) 2000 by Michael Edwardes (C) 2008 by Thomas Baumgart (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. * * * ***************************************************************************/ #include "mymoneypayee.h" #include "mymoneypayee_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include #include "mymoneystoragenames.h" using namespace MyMoneyStorageNodes; MyMoneyPayee MyMoneyPayee::null; MyMoneyPayee::MyMoneyPayee() : - d_ptr(new MyMoneyPayeePrivate) + MyMoneyObject(*new MyMoneyPayeePrivate) { } MyMoneyPayee::MyMoneyPayee(const QString& name, const QString& address, const QString& city, const QString& state, const QString& postcode, const QString& telephone, const QString& email) : - d_ptr(new MyMoneyPayeePrivate) + MyMoneyObject(*new MyMoneyPayeePrivate) { Q_D(MyMoneyPayee); d->m_name = name; d->m_address = address; d->m_city = city; d->m_state = state; d->m_postcode = postcode; d->m_telephone = telephone; d->m_email = email; d->m_matchingEnabled = false; d->m_usingMatchKey = false; d->m_matchKeyIgnoreCase = true; } MyMoneyPayee::MyMoneyPayee(const QDomElement& node) : - MyMoneyObject(node), - d_ptr(new MyMoneyPayeePrivate) + MyMoneyObject(*new MyMoneyPayeePrivate, node) { if (nodeNames[nnPayee] != node.tagName()) { throw MYMONEYEXCEPTION("Node was not PAYEE"); } Q_D(MyMoneyPayee); d->m_name = node.attribute(d->getAttrName(Payee::Attribute::Name)); d->m_reference = node.attribute(d->getAttrName(Payee::Attribute::Reference)); d->m_email = node.attribute(d->getAttrName(Payee::Attribute::Email)); d->m_matchingEnabled = node.attribute(d->getAttrName(Payee::Attribute::MatchingEnabled), "0").toUInt(); if (d->m_matchingEnabled) { setMatchData((node.attribute(d->getAttrName(Payee::Attribute::UsingMatchKey), "0").toUInt() != 0) ? matchKey : matchName, node.attribute(d->getAttrName(Payee::Attribute::MatchIgnoreCase), "0").toUInt(), node.attribute(d->getAttrName(Payee::Attribute::MatchKey))); } if (node.hasAttribute(d->getAttrName(Payee::Attribute::Notes))) { d->m_notes = node.attribute(d->getAttrName(Payee::Attribute::Notes)); } if (node.hasAttribute(d->getAttrName(Payee::Attribute::DefaultAccountID))) { d->m_defaultAccountId = node.attribute(d->getAttrName(Payee::Attribute::DefaultAccountID)); } // Load Address QDomNodeList nodeList = node.elementsByTagName(d->getElName(Payee::Element::Address)); if (nodeList.count() == 0) { QString msg = QString("No ADDRESS in payee %1").arg(d->m_name); throw MYMONEYEXCEPTION(msg); } QDomElement addrNode = nodeList.item(0).toElement(); d->m_address = addrNode.attribute(d->getAttrName(Payee::Attribute::Street)); d->m_city = addrNode.attribute(d->getAttrName(Payee::Attribute::City)); d->m_postcode = addrNode.attribute(d->getAttrName(Payee::Attribute::PostCode)); d->m_state = addrNode.attribute(d->getAttrName(Payee::Attribute::State)); d->m_telephone = addrNode.attribute(d->getAttrName(Payee::Attribute::Telephone)); MyMoneyPayeeIdentifierContainer::loadXML(node); } MyMoneyPayee::MyMoneyPayee(const MyMoneyPayee& other) : - MyMoneyObject(other.id()), - MyMoneyPayeeIdentifierContainer(other), - d_ptr(new MyMoneyPayeePrivate(*other.d_func())) + MyMoneyObject(*new MyMoneyPayeePrivate(*other.d_func()), other.id()), + MyMoneyPayeeIdentifierContainer(other) { } MyMoneyPayee::MyMoneyPayee(const QString& id, const MyMoneyPayee& other) : - MyMoneyObject(id), - MyMoneyPayeeIdentifierContainer(other), - d_ptr(new MyMoneyPayeePrivate(*other.d_func())) + MyMoneyObject(*new MyMoneyPayeePrivate(*other.d_func()), id), + MyMoneyPayeeIdentifierContainer(other) { } MyMoneyPayee::~MyMoneyPayee() { - Q_D(MyMoneyPayee); - delete d; } bool MyMoneyPayee::operator == (const MyMoneyPayee& right) const { Q_D(const MyMoneyPayee); auto d2 = static_cast(right.d_func()); return (MyMoneyObject::operator==(right) && ((d->m_name.length() == 0 && d2->m_name.length() == 0) || (d->m_name == d2->m_name)) && ((d->m_address.length() == 0 && d2->m_address.length() == 0) || (d->m_address == d2->m_address)) && ((d->m_city.length() == 0 && d2->m_city.length() == 0) || (d->m_city == d2->m_city)) && ((d->m_state.length() == 0 && d2->m_state.length() == 0) || (d->m_state == d2->m_state)) && ((d->m_postcode.length() == 0 && d2->m_postcode.length() == 0) || (d->m_postcode == d2->m_postcode)) && ((d->m_telephone.length() == 0 && d2->m_telephone.length() == 0) || (d->m_telephone == d2->m_telephone)) && ((d->m_email.length() == 0 && d2->m_email.length() == 0) || (d->m_email == d2->m_email)) && (d->m_matchingEnabled == d2->m_matchingEnabled) && (d->m_usingMatchKey == d2->m_usingMatchKey) && (d->m_matchKeyIgnoreCase == d2->m_matchKeyIgnoreCase) && ((d->m_matchKey.length() == 0 && d2->m_matchKey.length() == 0) || d->m_matchKey == d2->m_matchKey) && ((d->m_reference.length() == 0 && d2->m_reference.length() == 0) || (d->m_reference == d2->m_reference)) && ((d->m_defaultAccountId.length() == 0 && d2->m_defaultAccountId.length() == 0) || d->m_defaultAccountId == d2->m_defaultAccountId)); } //bool MyMoneyPayee::operator == (const MyMoneyPayee& lhs, const QString& rhs) const //{ // Q_D(const MyMoneyPayee); // return lhs.id() == rhs; //} bool MyMoneyPayee::operator < (const MyMoneyPayee& right) const { Q_D(const MyMoneyPayee); auto d2 = static_cast(right.d_func()); return d->m_name < d2->m_name; } void MyMoneyPayee::writeXML(QDomDocument& document, QDomElement& parent) const { auto el = document.createElement(nodeNames[nnPayee]); - writeBaseXML(document, el); - Q_D(const MyMoneyPayee); + d->writeBaseXML(document, el); + el.setAttribute(d->getAttrName(Payee::Attribute::Name), d->m_name); el.setAttribute(d->getAttrName(Payee::Attribute::Reference), d->m_reference); el.setAttribute(d->getAttrName(Payee::Attribute::Email), d->m_email); if (!d->m_notes.isEmpty()) el.setAttribute(d->getAttrName(Payee::Attribute::Notes), d->m_notes); el.setAttribute(d->getAttrName(Payee::Attribute::MatchingEnabled), d->m_matchingEnabled); if (d->m_matchingEnabled) { el.setAttribute(d->getAttrName(Payee::Attribute::UsingMatchKey), d->m_usingMatchKey); el.setAttribute(d->getAttrName(Payee::Attribute::MatchIgnoreCase), d->m_matchKeyIgnoreCase); el.setAttribute(d->getAttrName(Payee::Attribute::MatchKey), d->m_matchKey); } if (!d->m_defaultAccountId.isEmpty()) { el.setAttribute(d->getAttrName(Payee::Attribute::DefaultAccountID), d->m_defaultAccountId); } // Save address QDomElement address = document.createElement(d->getElName(Payee::Element::Address)); address.setAttribute(d->getAttrName(Payee::Attribute::Street), d->m_address); address.setAttribute(d->getAttrName(Payee::Attribute::City), d->m_city); address.setAttribute(d->getAttrName(Payee::Attribute::PostCode), d->m_postcode); address.setAttribute(d->getAttrName(Payee::Attribute::State), d->m_state); address.setAttribute(d->getAttrName(Payee::Attribute::Telephone), d->m_telephone); el.appendChild(address); // Save payeeIdentifiers (account numbers) MyMoneyPayeeIdentifierContainer::writeXML(document, el); parent.appendChild(el); } bool MyMoneyPayee::hasReferenceTo(const QString& id) const { Q_D(const MyMoneyPayee); return id == d->m_defaultAccountId; } QString MyMoneyPayee::name() const { Q_D(const MyMoneyPayee); return d->m_name; } void MyMoneyPayee::setName(const QString& val) { Q_D(MyMoneyPayee); d->m_name = val; } QString MyMoneyPayee::address() const { Q_D(const MyMoneyPayee); return d->m_address; } void MyMoneyPayee::setAddress(const QString& val) { Q_D(MyMoneyPayee); d->m_address = val; } QString MyMoneyPayee::city() const { Q_D(const MyMoneyPayee); return d->m_city; } void MyMoneyPayee::setCity(const QString& val) { Q_D(MyMoneyPayee); d->m_city = val; } QString MyMoneyPayee::state() const { Q_D(const MyMoneyPayee); return d->m_state; } void MyMoneyPayee::setState(const QString& val) { Q_D(MyMoneyPayee); d->m_state = val; } QString MyMoneyPayee::postcode() const { Q_D(const MyMoneyPayee); return d->m_postcode; } void MyMoneyPayee::setPostcode(const QString& val) { Q_D(MyMoneyPayee); d->m_postcode = val; } QString MyMoneyPayee::telephone() const { Q_D(const MyMoneyPayee); return d->m_telephone; } void MyMoneyPayee::setTelephone(const QString& val) { Q_D(MyMoneyPayee); d->m_telephone = val; } QString MyMoneyPayee::email() const { Q_D(const MyMoneyPayee); return d->m_email; } void MyMoneyPayee::setEmail(const QString& val) { Q_D(MyMoneyPayee); d->m_email = val; } QString MyMoneyPayee::notes() const { Q_D(const MyMoneyPayee); return d->m_notes; } void MyMoneyPayee::setNotes(const QString& val) { Q_D(MyMoneyPayee); d->m_notes = val; } QString MyMoneyPayee::reference() const { Q_D(const MyMoneyPayee); return d->m_reference; } void MyMoneyPayee::setReference(const QString& ref) { Q_D(MyMoneyPayee); d->m_reference = ref; } MyMoneyPayee::payeeMatchType MyMoneyPayee::matchData(bool& ignorecase, QStringList& keys) const { payeeMatchType type = matchDisabled; keys.clear(); Q_D(const MyMoneyPayee); ignorecase = d->m_matchKeyIgnoreCase; if (d->m_matchingEnabled) { type = d->m_usingMatchKey ? matchKey : matchName; if (type == matchKey) { if (d->m_matchKey.contains(QLatin1Char('\n'))) keys = d->m_matchKey.split(QLatin1Char('\n')); else keys = d->m_matchKey.split(QLatin1Char(';')); // for compatibility with 4.8.0 } else if (d->m_matchKey.compare(QLatin1String("^$")) == 0) { type = matchNameExact; } } return type; } MyMoneyPayee::payeeMatchType MyMoneyPayee::matchData(bool& ignorecase, QString& keyString) const { QStringList keys; payeeMatchType type = matchData(ignorecase, keys); keyString = keys.join(QLatin1Char('\n')); return type; } void MyMoneyPayee::setMatchData(payeeMatchType type, bool ignorecase, const QStringList& keys) { Q_D(MyMoneyPayee); d->m_matchingEnabled = (type != matchDisabled); d->m_matchKeyIgnoreCase = ignorecase; d->m_matchKey.clear(); if (d->m_matchingEnabled) { d->m_usingMatchKey = (type == matchKey); if (d->m_usingMatchKey) { QRegExp validKeyRegExp("[^ ]"); QStringList filteredKeys = keys.filter(validKeyRegExp); d->m_matchKey = filteredKeys.join(QLatin1Char('\n')); } else if(type == matchNameExact) { d->m_matchKey = QLatin1String("^$"); } } } void MyMoneyPayee::setMatchData(payeeMatchType type, bool ignorecase, const QString& keys) { if (keys.contains(QLatin1Char('\n'))) setMatchData(type, ignorecase, keys.split(QLatin1Char('\n'))); else setMatchData(type, ignorecase, keys.split(QLatin1Char(';'))); // for compatibility with 4.8.0 } bool MyMoneyPayee::defaultAccountEnabled() const { Q_D(const MyMoneyPayee); return !d->m_defaultAccountId.isEmpty(); } QString MyMoneyPayee::defaultAccountId() const { Q_D(const MyMoneyPayee); return d->m_defaultAccountId; } void MyMoneyPayee::setDefaultAccountId(const QString& id) { Q_D(MyMoneyPayee); d->m_defaultAccountId = id; } void MyMoneyPayee::setDefaultAccountId() { setDefaultAccountId(QString()); } // vim:cin:si:ai:et:ts=2:sw=2: diff --git a/kmymoney/mymoney/mymoneypayee.h b/kmymoney/mymoney/mymoneypayee.h index b0d0d6bbc..954a02f12 100644 --- a/kmymoney/mymoney/mymoneypayee.h +++ b/kmymoney/mymoney/mymoneypayee.h @@ -1,208 +1,206 @@ /*************************************************************************** mymoneypayee.h ------------------- copyright : (C) 2000 by Michael Edwardes 2005 by Thomas Baumgart (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 MYMONEYPAYEE_H #define MYMONEYPAYEE_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "kmm_mymoney_export.h" #include "mymoneyobject.h" #include "mymoneypayeeidentifiercontainer.h" class QString; class QStringList; /** * This class represents a payee or receiver within the MyMoney engine. * Since it is not payee-specific, it is also used as a generic address * book entry. * * @author Thomas Baumgart */ class MyMoneyPayeePrivate; class KMM_MYMONEY_EXPORT MyMoneyPayee : public MyMoneyObject, public MyMoneyPayeeIdentifierContainer { Q_DECLARE_PRIVATE(MyMoneyPayee) - MyMoneyPayeePrivate* d_ptr; KMM_MYMONEY_UNIT_TESTABLE public: typedef enum { matchDisabled = 0, matchName, matchKey, matchNameExact } payeeMatchType; MyMoneyPayee(); explicit MyMoneyPayee(const QString& name, const QString& address = QString(), const QString& city = QString(), const QString& state = QString(), const QString& postcode = QString(), const QString& telephone = QString(), const QString& email = QString()); /** * This is the constructor for a payee that is described by a * QDomElement (e.g. from a file). * * @param el const reference to the QDomElement from which to * create the object */ explicit MyMoneyPayee(const QDomElement& node); MyMoneyPayee(const QString& id, const MyMoneyPayee& other); MyMoneyPayee(const MyMoneyPayee & other); MyMoneyPayee(MyMoneyPayee && other); MyMoneyPayee & operator=(MyMoneyPayee other); friend void swap(MyMoneyPayee& first, MyMoneyPayee& second); ~MyMoneyPayee(); QString name() const; void setName(const QString& val); QString address() const; void setAddress(const QString& val); QString city() const; void setCity(const QString& val); QString state() const; void setState(const QString& val); QString postcode() const; void setPostcode(const QString& val); QString telephone() const; void setTelephone(const QString& val); QString email() const; void setEmail(const QString& val); QString notes() const; void setNotes(const QString& val); QString reference() const; void setReference(const QString& ref); /** * Get all match data in one call * * @param ignorecase Bool which will be replaced to indicate whether the match is * case-sensitive (false) or case-insensitive (true) * @param keys List of strings which will be replaced by the match key to use for this payee * * @return the matching type (see payeeMatchType for details) */ payeeMatchType matchData(bool& ignorecase, QStringList& keys) const; /** * Set all match data in one call * * @param type matching type (see payeeMatchType for details) * @param ignorecase Whether case should be ignored for the key/name match * @param keys A list of keys themselves, if applicable */ void setMatchData(payeeMatchType type, bool ignorecase, const QStringList& keys); /** * Get all match data in one call (overloaded version for database module) * * @param ignorecase Bool which will be replaced to indicate whether the match is * case-sensitive (false) or case-insensitive (true) * @param keyString A list of keys in single-string format, if applicable * * @return the matching type (see payeeMatchType for details) */ payeeMatchType matchData(bool& ignorecase, QString& keyString) const; /** * Set all match data in one call (overloaded version for database module) * * @param type matching type (see payeeMatchType for details) * @param ignorecase Whether case should be ignored for the key/name match * @param keys A list of keys in single-string format, if applicable */ void setMatchData(payeeMatchType type, bool ignorecase, const QString& keys); bool defaultAccountEnabled() const; QString defaultAccountId() const; void setDefaultAccountId(const QString& id); void setDefaultAccountId(); // Equality operator bool operator == (const MyMoneyPayee &) const; // bool operator == (const MyMoneyPayee& lhs, const QString& rhs) const; bool operator <(const MyMoneyPayee& right) const; void writeXML(QDomDocument& document, QDomElement& parent) const override; /** * This method checks if a reference to the given object exists. It returns, * a @p true if the object is referencing the one requested by the * parameter @p id. If it does not, this method returns @p false. * * @param id id of the object to be checked for references * @retval true This object references object with id @p id. * @retval false This object does not reference the object with id @p id. */ bool hasReferenceTo(const QString& id) const override; static MyMoneyPayee null; }; inline void swap(MyMoneyPayee& first, MyMoneyPayee& second) // krazy:exclude=inline { using std::swap; swap(first.d_ptr, second.d_ptr); - swap(first.m_id, second.m_id); swap(first.m_payeeIdentifiers, second.m_payeeIdentifiers); } inline MyMoneyPayee::MyMoneyPayee(MyMoneyPayee && other) : MyMoneyPayee() // krazy:exclude=inline { swap(*this, other); } inline MyMoneyPayee & MyMoneyPayee::operator=(MyMoneyPayee other) // krazy:exclude=inline { swap(*this, other); return *this; } /** * Make it possible to hold @ref MyMoneyPayee objects inside @ref QVariant objects. */ Q_DECLARE_METATYPE(MyMoneyPayee) #endif diff --git a/kmymoney/mymoney/mymoneypayee_p.h b/kmymoney/mymoney/mymoneypayee_p.h index 894cd1274..79ff8aaa1 100644 --- a/kmymoney/mymoney/mymoneypayee_p.h +++ b/kmymoney/mymoney/mymoneypayee_p.h @@ -1,139 +1,141 @@ /*************************************************************************** mymoneypayee.cpp ------------------- copyright : (C) 2000 by Michael Edwardes (C) 2008 by Thomas Baumgart (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 MYMONEYPAYEE_P_H #define MYMONEYPAYEE_P_H // ---------------------------------------------------------------------------- // QT Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes +#include "mymoneyobject_p.h" + namespace Payee { enum class Element { Address }; uint qHash(const Element key, uint seed) { return ::qHash(static_cast(key), seed); } enum class Attribute { Name = 0, Type, Reference, Notes, MatchingEnabled, UsingMatchKey, MatchIgnoreCase, MatchKey, DefaultAccountID, Street, City, PostCode, Email, State, Telephone, // insert new entries above this line LastAttribute }; uint qHash(const Attribute key, uint seed) { return ::qHash(static_cast(key), seed); } } -class MyMoneyPayeePrivate { - +class MyMoneyPayeePrivate : public MyMoneyObjectPrivate +{ public: MyMoneyPayeePrivate() : m_matchingEnabled(false), m_usingMatchKey(false), m_matchKeyIgnoreCase(true) { } static QString getElName(const Payee::Element el) { static const QMap elNames { {Payee::Element::Address, QStringLiteral("ADDRESS")} }; return elNames[el]; } static QString getAttrName(const Payee::Attribute attr) { static const QHash attrNames { {Payee::Attribute::Name, QStringLiteral("name")}, {Payee::Attribute::Type, QStringLiteral("type")}, {Payee::Attribute::Reference, QStringLiteral("reference")}, {Payee::Attribute::Notes, QStringLiteral("notes")}, {Payee::Attribute::MatchingEnabled, QStringLiteral("matchingenabled")}, {Payee::Attribute::UsingMatchKey, QStringLiteral("usingmatchkey")}, {Payee::Attribute::MatchIgnoreCase, QStringLiteral("matchignorecase")}, {Payee::Attribute::MatchKey, QStringLiteral("matchkey")}, {Payee::Attribute::DefaultAccountID, QStringLiteral("defaultaccountid")}, {Payee::Attribute::Street, QStringLiteral("street")}, {Payee::Attribute::City, QStringLiteral("city")}, {Payee::Attribute::PostCode, QStringLiteral("postcode")}, {Payee::Attribute::Email, QStringLiteral("email")}, {Payee::Attribute::State, QStringLiteral("state")}, {Payee::Attribute::Telephone, QStringLiteral("telephone")}, }; return attrNames[attr]; } // Simple fields QString m_name; QString m_address; QString m_city; QString m_state; QString m_postcode; QString m_telephone; QString m_email; QString m_notes; // Transaction matching fields bool m_matchingEnabled; //< Whether this payee should be matched at all bool m_usingMatchKey; //< If so, whether a m_matchKey list is used (true), or just m_name is used (false) bool m_matchKeyIgnoreCase; //< Whether to ignore the case of the match key or name /** * Semicolon separated list of matching keys used when trying to find a suitable * payee for imported transactions. */ QString m_matchKey; // Category (account) matching fields QString m_defaultAccountId; /** * This member keeps a reference to an external database * (e.g. kaddressbook). It is the responsibility of the * application to format the reference string * (e.g. encoding the name of the external database into the * reference string). * If no external database is available it should be kept * empty by the application. */ QString m_reference; }; #endif // vim:cin:si:ai:et:ts=2:sw=2: diff --git a/kmymoney/mymoney/mymoneyreport.cpp b/kmymoney/mymoney/mymoneyreport.cpp index 12b27077c..4fe0e7229 100644 --- a/kmymoney/mymoney/mymoneyreport.cpp +++ b/kmymoney/mymoney/mymoneyreport.cpp @@ -1,1633 +1,1628 @@ /*************************************************************************** mymoneyreport.cpp ------------------- begin : Sun July 4 2004 copyright : (C) 2004-2005 by Ace Jones email : acejones@users.sourceforge.net (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. * * * ***************************************************************************/ #include "mymoneyreport_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneymoney.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneystoragenames.h" #include "mymoneytransaction.h" #include "mymoneytransactionfilter.h" #include "mymoneyexception.h" #include "kmymoneyglobalsettings.h" using namespace MyMoneyStorageNodes; // define this to debug reports // #define DEBUG_REPORTS const QStringList MyMoneyReport::kRowTypeText = QString("none,assetliability,expenseincome,category,topcategory,account,tag,payee,month,week,topaccount,topaccount-account,equitytype,accounttype,institution,budget,budgetactual,schedule,accountinfo,accountloaninfo,accountreconcile,cashflow").split(','); const QStringList MyMoneyReport::kColumnTypeText = QString("none,months,bimonths,quarters,4,5,6,weeks,8,9,10,11,years").split(','); // if you add names here, don't forget to update the bitmap for EQueryColumns // and shift the bit for eQCend one position to the left const QStringList MyMoneyReport::kQueryColumnsText = QString("none,number,payee,category,tag,memo,account,reconcileflag,action,shares,price,performance,loan,balance,capitalgain").split(','); const MyMoneyReport::EReportType MyMoneyReport::kTypeArray[] = { eNoReport, ePivotTable, ePivotTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, ePivotTable, ePivotTable, eInfoTable, eInfoTable, eInfoTable, eQueryTable, eQueryTable, eNoReport }; const QStringList MyMoneyReport::kDetailLevelText = QString("none,all,top,group,total,invalid").split(','); const QStringList MyMoneyReport::kChartTypeText = QString("none,line,bar,pie,ring,stackedbar").split(','); // This should live in mymoney/mymoneytransactionfilter.h const QStringList kTypeText = QString("all,payments,deposits,transfers,none").split(','); const QStringList kStateText = QString("all,notreconciled,cleared,reconciled,frozen,none").split(','); const QStringList kDateLockText = QString("alldates,untiltoday,currentmonth,currentyear,monthtodate,yeartodate,yeartomonth,lastmonth,lastyear,last7days,last30days,last3months,last6months,last12months,next7days,next30days,next3months,next6months,next12months,userdefined,last3tonext3months,last11Months,currentQuarter,lastQuarter,nextQuarter,currentFiscalYear,lastFiscalYear,today,next18months").split(','); const QStringList kDataLockText = QString("automatic,userdefined").split(','); const QStringList kAccountTypeText = QString("unknown,checkings,savings,cash,creditcard,loan,certificatedep,investment,moneymarket,asset,liability,currency,income,expense,assetloan,stock,equity,invalid").split(','); MyMoneyReport::MyMoneyReport() : - d_ptr(new MyMoneyReportPrivate) + MyMoneyObject(*new MyMoneyReportPrivate) { Q_D(MyMoneyReport); d->m_name = "Unconfigured Pivot Table Report"; d->m_detailLevel = eDetailNone; d->m_investmentSum = eSumSold; d->m_hideTransactions = false; d->m_convertCurrency = true; d->m_favorite = false; d->m_tax = false; d->m_investments = false; d->m_loans = false; d->m_reportType = kTypeArray[eExpenseIncome]; d->m_rowType = eExpenseIncome; d->m_columnType = eMonths; d->m_columnsAreDays = false; d->m_queryColumns = eQCnone; d->m_dateLock = TransactionFilter::Date::UserDefined; d->m_accountGroupFilter = false; d->m_chartType = eChartLine; d->m_chartDataLabels = true; d->m_chartCHGridLines = true; d->m_chartSVGridLines = true; d->m_chartByDefault = false; d->m_logYaxis = false; d->m_dataRangeStart = '0'; d->m_dataRangeEnd = '0'; d->m_dataMajorTick = '0'; d->m_dataMinorTick = '0'; d->m_yLabelsPrecision = 2; d->m_dataLock = MyMoneyReport::automatic; d->m_includeSchedules = false; d->m_includeTransfers = false; d->m_includeBudgetActuals = false; d->m_includeUnusedAccounts = false; d->m_showRowTotals = false; d->m_showColumnTotals = true; d->m_includeForecast = false; d->m_includeMovingAverage = false; d->m_movingAverageDays = 0; d->m_includePrice = false; d->m_includeAveragePrice = false; d->m_mixedTime = false; d->m_currentDateColumn = 0; d->m_settlementPeriod = 3; d->m_showSTLTCapitalGains = false; d->m_tseparator = QDate::currentDate().addYears(-1); d->m_skipZero = false; d->m_chartLineWidth = m_lineWidth; } MyMoneyReport::MyMoneyReport(ERowType rt, unsigned ct, TransactionFilter::Date dl, EDetailLevel ss, const QString& name, const QString& comment) : - d_ptr(new MyMoneyReportPrivate) + MyMoneyObject(*new MyMoneyReportPrivate) { Q_D(MyMoneyReport); d->m_name = name; d->m_comment = comment; d->m_detailLevel = ss; d->m_investmentSum = ct & eQCcapitalgain ? eSumSold : eSumPeriod; d->m_hideTransactions = false; d->m_convertCurrency = true; d->m_favorite = false; d->m_tax = false; d->m_investments = false; d->m_loans = false; d->m_reportType = kTypeArray[rt]; d->m_rowType = rt; d->m_columnType = eMonths; d->m_columnsAreDays = false; d->m_queryColumns = eQCnone; d->m_dateLock = dl; d->m_accountGroupFilter = false; d->m_chartType = eChartLine; d->m_chartDataLabels = true; d->m_chartCHGridLines = true; d->m_chartSVGridLines = true; d->m_chartByDefault = false; d->m_logYaxis = false; d->m_dataRangeStart = '0'; d->m_dataRangeEnd = '0'; d->m_dataMajorTick = '0'; d->m_dataMinorTick = '0'; d->m_yLabelsPrecision = 2; d->m_dataLock = MyMoneyReport::automatic; d->m_includeSchedules = false; d->m_includeTransfers = false; d->m_includeBudgetActuals = false; d->m_includeUnusedAccounts = false; d->m_showRowTotals = false; d->m_showColumnTotals = true; d->m_includeForecast = false; d->m_includeMovingAverage = false; d->m_movingAverageDays = 0; d->m_includePrice = false; d->m_includeAveragePrice = false; d->m_mixedTime = false; d->m_currentDateColumn = 0; d->m_settlementPeriod = 3; d->m_showSTLTCapitalGains = false; d->m_tseparator = QDate::currentDate().addYears(-1); d->m_skipZero = false; //set initial values d->m_chartLineWidth = m_lineWidth; //set report type if (d->m_reportType == ePivotTable) d->m_columnType = static_cast(ct); if (d->m_reportType == eQueryTable) d->m_queryColumns = static_cast(ct); setDateFilter(dl); //throw exception if the type is inconsistent if ((rt > static_cast(sizeof(kTypeArray) / sizeof(kTypeArray[0]))) || (d->m_reportType == eNoReport)) throw MYMONEYEXCEPTION("Invalid report type"); //add the corresponding account groups if (rt == MyMoneyReport::eAssetLiability) { addAccountGroup(Account::Type::Asset); addAccountGroup(Account::Type::Liability); d->m_showRowTotals = true; } if (rt == MyMoneyReport::eAccount) { addAccountGroup(Account::Type::Asset); addAccountGroup(Account::Type::AssetLoan); addAccountGroup(Account::Type::Cash); addAccountGroup(Account::Type::Checkings); addAccountGroup(Account::Type::CreditCard); if (KMyMoneyGlobalSettings::expertMode()) addAccountGroup(Account::Type::Equity); addAccountGroup(Account::Type::Expense); addAccountGroup(Account::Type::Income); addAccountGroup(Account::Type::Liability); addAccountGroup(Account::Type::Loan); addAccountGroup(Account::Type::Savings); addAccountGroup(Account::Type::Stock); d->m_showRowTotals = true; } if (rt == MyMoneyReport::eExpenseIncome) { addAccountGroup(Account::Type::Expense); addAccountGroup(Account::Type::Income); d->m_showRowTotals = true; } //FIXME take this out once we have sorted out all issues regarding budget of assets and liabilities -- asoliverez@gmail.com if (rt == MyMoneyReport::eBudget || rt == MyMoneyReport::eBudgetActual) { addAccountGroup(Account::Type::Expense); addAccountGroup(Account::Type::Income); } if (rt == MyMoneyReport::eAccountInfo) { addAccountGroup(Account::Type::Asset); addAccountGroup(Account::Type::Liability); } //cash flow reports show splits for all account groups if (rt == MyMoneyReport::eCashFlow) { addAccountGroup(Account::Type::Expense); addAccountGroup(Account::Type::Income); addAccountGroup(Account::Type::Asset); addAccountGroup(Account::Type::Liability); } #ifdef DEBUG_REPORTS QDebug out = qDebug(); out << _name << toString(_rt) << toString(m_reportType); foreach(const Account::Type accountType, m_accountGroups) out << MyMoneyAccount::accountTypeToString(accountType); if (m_accounts.size() > 0) out << m_accounts; #endif } MyMoneyReport::MyMoneyReport(const QDomElement& node) : - MyMoneyObject(node), - d_ptr(new MyMoneyReportPrivate) + MyMoneyObject(*new MyMoneyReportPrivate, node) { Q_D(MyMoneyReport); d->m_currentDateColumn = 0; // properly initialize the object before reading it *this = MyMoneyReport(); if (!read(node)) clearId(); } MyMoneyReport::MyMoneyReport(const MyMoneyReport& other) : - MyMoneyObject(other.id()), - MyMoneyTransactionFilter(other), - d_ptr(new MyMoneyReportPrivate(*other.d_func())) + MyMoneyObject(*new MyMoneyReportPrivate(*other.d_func()), other.id()), + MyMoneyTransactionFilter(other) { } MyMoneyReport::MyMoneyReport(const QString& id, const MyMoneyReport& other) : - MyMoneyObject(id), - MyMoneyTransactionFilter(other), - d_ptr(new MyMoneyReportPrivate(*other.d_func())) + MyMoneyObject(*new MyMoneyReportPrivate(*other.d_func()), id), + MyMoneyTransactionFilter(other) { Q_D(MyMoneyReport); d->m_movingAverageDays = 0; d->m_currentDateColumn = 0; } MyMoneyReport::~MyMoneyReport() { - Q_D(MyMoneyReport); - delete d; } MyMoneyReport::EReportType MyMoneyReport::reportType() const { Q_D(const MyMoneyReport); return d->m_reportType; } QString MyMoneyReport::name() const { Q_D(const MyMoneyReport); return d->m_name; } void MyMoneyReport::setName(const QString& s) { Q_D(MyMoneyReport); d->m_name = s; } bool MyMoneyReport::isShowingRowTotals() const { Q_D(const MyMoneyReport); return (d->m_showRowTotals); } void MyMoneyReport::setShowingRowTotals(bool f) { Q_D(MyMoneyReport); d->m_showRowTotals = f; } bool MyMoneyReport::isShowingColumnTotals() const { Q_D(const MyMoneyReport); return d->m_showColumnTotals; } void MyMoneyReport::setShowingColumnTotals(bool f) { Q_D(MyMoneyReport); d->m_showColumnTotals = f; } MyMoneyReport::ERowType MyMoneyReport::rowType() const { Q_D(const MyMoneyReport); return d->m_rowType; } void MyMoneyReport::setRowType(ERowType rt) { Q_D(MyMoneyReport); d->m_rowType = rt; d->m_reportType = kTypeArray[rt]; d->m_accountGroupFilter = false; d->m_accountGroups.clear(); if (rt == MyMoneyReport::eAssetLiability) { addAccountGroup(Account::Type::Asset); addAccountGroup(Account::Type::Liability); } if (rt == MyMoneyReport::eExpenseIncome) { addAccountGroup(Account::Type::Expense); addAccountGroup(Account::Type::Income); } } bool MyMoneyReport::isRunningSum() const { Q_D(const MyMoneyReport); return (d->m_rowType == eAssetLiability); } MyMoneyReport::EColumnType MyMoneyReport::columnType() const { Q_D(const MyMoneyReport); return d->m_columnType; } void MyMoneyReport::setColumnType(EColumnType ct) { Q_D(MyMoneyReport); d->m_columnType = ct; } bool MyMoneyReport::isConvertCurrency() const { Q_D(const MyMoneyReport); return d->m_convertCurrency; } void MyMoneyReport::setConvertCurrency(bool f) { Q_D(MyMoneyReport); d->m_convertCurrency = f; } uint MyMoneyReport::columnPitch() const { Q_D(const MyMoneyReport); return static_cast(d->m_columnType); } QString MyMoneyReport::comment() const { Q_D(const MyMoneyReport); return d->m_comment; } void MyMoneyReport::setComment(const QString& comment) { Q_D(MyMoneyReport); d->m_comment = comment; } MyMoneyReport::EQueryColumns MyMoneyReport::queryColumns() const { Q_D(const MyMoneyReport); return d->m_queryColumns; } void MyMoneyReport::setQueryColumns(EQueryColumns qc) { Q_D(MyMoneyReport); d->m_queryColumns = qc; } QString MyMoneyReport::group() const { Q_D(const MyMoneyReport); return d->m_group; } void MyMoneyReport::setGroup(const QString& group) { Q_D(MyMoneyReport); d->m_group = group; } bool MyMoneyReport::isFavorite() const { Q_D(const MyMoneyReport); return d->m_favorite; } void MyMoneyReport::setFavorite(bool f) { Q_D(MyMoneyReport); d->m_favorite = f; } bool MyMoneyReport::isTax() const { Q_D(const MyMoneyReport); return d->m_tax; } void MyMoneyReport::setTax(bool f) { Q_D(MyMoneyReport); d->m_tax = f; } bool MyMoneyReport::isInvestmentsOnly() const { Q_D(const MyMoneyReport); return d->m_investments; } void MyMoneyReport::setInvestmentsOnly(bool f) { Q_D(MyMoneyReport); d->m_investments = f; if (f) d->m_loans = false; } bool MyMoneyReport::isLoansOnly() const { Q_D(const MyMoneyReport); return d->m_loans; } void MyMoneyReport::setLoansOnly(bool f) { Q_D(MyMoneyReport); d->m_loans = f; if (f) d->m_investments = false; } MyMoneyReport::EDetailLevel MyMoneyReport::detailLevel() const { Q_D(const MyMoneyReport); return d->m_detailLevel; } void MyMoneyReport::setDetailLevel(EDetailLevel detail) { Q_D(MyMoneyReport); d->m_detailLevel = detail; } MyMoneyReport::EInvestmentSum MyMoneyReport::investmentSum() const { Q_D(const MyMoneyReport); return d->m_investmentSum; } void MyMoneyReport::setInvestmentSum(EInvestmentSum sum) { Q_D(MyMoneyReport); d->m_investmentSum = sum; } bool MyMoneyReport::isHideTransactions() const { Q_D(const MyMoneyReport); return d->m_hideTransactions; } void MyMoneyReport::setHideTransactions(bool f) { Q_D(MyMoneyReport); d->m_hideTransactions = f; } MyMoneyReport::EChartType MyMoneyReport::chartType() const { Q_D(const MyMoneyReport); return d->m_chartType; } void MyMoneyReport::setChartType(EChartType type) { Q_D(MyMoneyReport); d->m_chartType = type; } bool MyMoneyReport::isChartDataLabels() const { Q_D(const MyMoneyReport); return d->m_chartDataLabels; } void MyMoneyReport::setChartDataLabels(bool f) { Q_D(MyMoneyReport); d->m_chartDataLabels = f; } bool MyMoneyReport::isChartCHGridLines() const { Q_D(const MyMoneyReport); return d->m_chartCHGridLines; } void MyMoneyReport::setChartCHGridLines(bool f) { Q_D(MyMoneyReport); d->m_chartCHGridLines = f; } bool MyMoneyReport::isChartSVGridLines() const { Q_D(const MyMoneyReport); return d->m_chartSVGridLines; } void MyMoneyReport::setChartSVGridLines(bool f) { Q_D(MyMoneyReport); d->m_chartSVGridLines = f; } bool MyMoneyReport::isChartByDefault() const { Q_D(const MyMoneyReport); return d->m_chartByDefault; } void MyMoneyReport::setChartByDefault(bool f) { Q_D(MyMoneyReport); d->m_chartByDefault = f; } uint MyMoneyReport::chartLineWidth() const { Q_D(const MyMoneyReport); return d->m_chartLineWidth; } void MyMoneyReport::setChartLineWidth(uint f) { Q_D(MyMoneyReport); d->m_chartLineWidth = f; } bool MyMoneyReport::isLogYAxis() const { Q_D(const MyMoneyReport); return d->m_logYaxis; } void MyMoneyReport::setLogYAxis(bool f) { Q_D(MyMoneyReport); d->m_logYaxis = f; } QString MyMoneyReport::dataRangeStart() const { Q_D(const MyMoneyReport); return d->m_dataRangeStart; } void MyMoneyReport::setDataRangeStart(const QString& f) { Q_D(MyMoneyReport); d->m_dataRangeStart = f; } QString MyMoneyReport::dataRangeEnd() const { Q_D(const MyMoneyReport); return d->m_dataRangeEnd; } void MyMoneyReport::setDataRangeEnd(const QString& f) { Q_D(MyMoneyReport); d->m_dataRangeEnd = f; } QString MyMoneyReport::dataMajorTick() const { Q_D(const MyMoneyReport); return d->m_dataMajorTick; } void MyMoneyReport::setDataMajorTick(const QString& f) { Q_D(MyMoneyReport); d->m_dataMajorTick = f; } QString MyMoneyReport::dataMinorTick() const { Q_D(const MyMoneyReport); return d->m_dataMinorTick; } void MyMoneyReport::setDataMinorTick(const QString& f) { Q_D(MyMoneyReport); d->m_dataMinorTick = f; } uint MyMoneyReport::yLabelsPrecision() const { Q_D(const MyMoneyReport); return d->m_yLabelsPrecision; } void MyMoneyReport::setYLabelsPrecision(int f) { Q_D(MyMoneyReport); d->m_yLabelsPrecision = f; } bool MyMoneyReport::isIncludingSchedules() const { Q_D(const MyMoneyReport); return d->m_includeSchedules; } void MyMoneyReport::setIncludingSchedules(bool f) { Q_D(MyMoneyReport); d->m_includeSchedules = f; } bool MyMoneyReport::isColumnsAreDays() const { Q_D(const MyMoneyReport); return d->m_columnsAreDays; } void MyMoneyReport::setColumnsAreDays(bool f) { Q_D(MyMoneyReport); d->m_columnsAreDays = f; } bool MyMoneyReport::isIncludingTransfers() const { Q_D(const MyMoneyReport); return d->m_includeTransfers; } void MyMoneyReport::setIncludingTransfers(bool f) { Q_D(MyMoneyReport); d->m_includeTransfers = f; } bool MyMoneyReport::isIncludingUnusedAccounts() const { Q_D(const MyMoneyReport); return d->m_includeUnusedAccounts; } void MyMoneyReport::setIncludingUnusedAccounts(bool f) { Q_D(MyMoneyReport); d->m_includeUnusedAccounts = f; } bool MyMoneyReport::hasBudget() const { Q_D(const MyMoneyReport); return !d->m_budgetId.isEmpty(); } QString MyMoneyReport::budget() const { Q_D(const MyMoneyReport); return d->m_budgetId; } /** * Sets the budget used for this report * * @param budget The ID of the budget to use, or an empty string * to indicate a budget is NOT included * @param fa Whether to display actual data alongside the budget. * Setting to false means the report displays ONLY the budget itself. * @warning For now, the budget ID is ignored. The budget id is * simply checked for any non-empty string, and if so, hasBudget() * will return true. */ void MyMoneyReport::setBudget(const QString& budget, bool fa) { Q_D(MyMoneyReport); d->m_budgetId = budget; d->m_includeBudgetActuals = fa; } bool MyMoneyReport::isIncludingBudgetActuals() const { Q_D(const MyMoneyReport); return d->m_includeBudgetActuals; } void MyMoneyReport::setIncludingBudgetActuals(bool f) { Q_D(MyMoneyReport); d->m_includeBudgetActuals = f; } bool MyMoneyReport::isIncludingForecast() const { Q_D(const MyMoneyReport); return d->m_includeForecast; } void MyMoneyReport::setIncludingForecast(bool f) { Q_D(MyMoneyReport); d->m_includeForecast = f; } bool MyMoneyReport::isIncludingMovingAverage() const { Q_D(const MyMoneyReport); return d->m_includeMovingAverage; } void MyMoneyReport::setIncludingMovingAverage(bool f) { Q_D(MyMoneyReport); d->m_includeMovingAverage = f; } int MyMoneyReport::movingAverageDays() const { Q_D(const MyMoneyReport); return d->m_movingAverageDays; } void MyMoneyReport::setMovingAverageDays(int days) { Q_D(MyMoneyReport); d->m_movingAverageDays = days; } bool MyMoneyReport::isIncludingPrice() const { Q_D(const MyMoneyReport); return d->m_includePrice; } void MyMoneyReport::setIncludingPrice(bool f) { Q_D(MyMoneyReport); d->m_includePrice = f; } bool MyMoneyReport::isIncludingAveragePrice() const { Q_D(const MyMoneyReport); return d->m_includeAveragePrice; } void MyMoneyReport::setIncludingAveragePrice(bool f) { Q_D(MyMoneyReport); d->m_includeAveragePrice = f; } MyMoneyReport::dataOptionE MyMoneyReport::dataFilter() const { Q_D(const MyMoneyReport); return d->m_dataLock; } bool MyMoneyReport::isDataUserDefined() const { Q_D(const MyMoneyReport); return d->m_dataLock == MyMoneyReport::userDefined; } void MyMoneyReport::setDataFilter(dataOptionE u) { Q_D(MyMoneyReport); d->m_dataLock = u; } eMyMoney::TransactionFilter::Date MyMoneyReport::dateRange() const { Q_D(const MyMoneyReport); return d->m_dateLock; } bool MyMoneyReport::isDateUserDefined() const { Q_D(const MyMoneyReport); return d->m_dateLock == TransactionFilter::Date::UserDefined; } /** * Set the underlying date filter and LOCK that filter to the specified * range. For example, if @p _u is "CurrentMonth", this report should always * be updated to the current month no matter when the report is run. * * This updating is not entirely automatic, you should update it yourself by * calling updateDateFilter. * * @param _u The date range constant (MyMoneyTransactionFilter::dateRangeE) * which this report should be locked to. */ void MyMoneyReport::setDateFilter(TransactionFilter::Date u) { Q_D(MyMoneyReport); d->m_dateLock = u; if (u != TransactionFilter::Date::UserDefined) MyMoneyTransactionFilter::setDateFilter(u); } void MyMoneyReport::setDateFilter(const QDate& db, const QDate& de) { MyMoneyTransactionFilter::setDateFilter(db, de); } void MyMoneyReport::updateDateFilter() { Q_D(MyMoneyReport); if (d->m_dateLock != TransactionFilter::Date::UserDefined) MyMoneyTransactionFilter::setDateFilter(d->m_dateLock); } bool MyMoneyReport::isMixedTime() const { Q_D(const MyMoneyReport); return d->m_mixedTime; } void MyMoneyReport::setMixedTime(bool f) { Q_D(MyMoneyReport); d->m_mixedTime = f; } int MyMoneyReport::currentDateColumn() const { Q_D(const MyMoneyReport); return d->m_currentDateColumn; } void MyMoneyReport::setCurrentDateColumn(int f) { Q_D(MyMoneyReport); d->m_currentDateColumn = f; } uint MyMoneyReport::settlementPeriod() const { Q_D(const MyMoneyReport); return d->m_settlementPeriod; } void MyMoneyReport::setSettlementPeriod(uint days) { Q_D(MyMoneyReport); d->m_settlementPeriod = days; } bool MyMoneyReport::isShowingSTLTCapitalGains() const { Q_D(const MyMoneyReport); return d->m_showSTLTCapitalGains; } void MyMoneyReport::setShowSTLTCapitalGains(bool f) { Q_D(MyMoneyReport); d->m_showSTLTCapitalGains = f; } QDate MyMoneyReport::termSeparator() const { Q_D(const MyMoneyReport); return d->m_tseparator; } void MyMoneyReport::setTermSeparator(const QDate& date) { Q_D(MyMoneyReport); d->m_tseparator = date; } bool MyMoneyReport::isSkippingZero() const { Q_D(const MyMoneyReport); return d->m_skipZero; } void MyMoneyReport::setSkipZero(int f) { Q_D(MyMoneyReport); d->m_skipZero = f; } void MyMoneyReport::clearTransactionFilter() { Q_D(MyMoneyReport); d->m_accountGroupFilter = false; d->m_accountGroups.clear(); MyMoneyTransactionFilter::clear(); } void MyMoneyReport::assignFilter(const MyMoneyTransactionFilter& filter) { MyMoneyTransactionFilter::operator=(filter); } void MyMoneyReport::validDateRange(QDate& db, QDate& de) { db = fromDate(); de = toDate(); // if either begin or end date are invalid we have one of the following // possible date filters: // // a) begin date not set - first transaction until given end date // b) end date not set - from given date until last transaction // c) both not set - first transaction until last transaction // // If there is no transaction in the engine at all, we use the current // year as the filter criteria. if (!db.isValid() || !de.isValid()) { QList list = MyMoneyFile::instance()->transactionList(*this); QDate tmpBegin, tmpEnd; if (!list.isEmpty()) { qSort(list); // try to use the post dates tmpBegin = list.front().postDate(); tmpEnd = list.back().postDate(); // if the post dates are not valid try the entry dates if (!tmpBegin.isValid()) tmpBegin = list.front().entryDate(); if (!tmpEnd.isValid()) tmpEnd = list.back().entryDate(); } // make sure that we leave this function with valid dates no mather what if (!tmpBegin.isValid() || !tmpEnd.isValid() || tmpBegin > tmpEnd) { tmpBegin = QDate(QDate::currentDate().year(), 1, 1); // the first date in the file tmpEnd = QDate(QDate::currentDate().year(), 12, 31); // the last date in the file } if (!db.isValid()) db = tmpBegin; if (!de.isValid()) de = tmpEnd; } if (db > de) db = de; } bool MyMoneyReport::accountGroups(QList& list) const { Q_D(const MyMoneyReport); bool result = d->m_accountGroupFilter; if (result) { QList::const_iterator it_group = d->m_accountGroups.begin(); while (it_group != d->m_accountGroups.end()) { list += (*it_group); ++it_group; } } return result; } void MyMoneyReport::addAccountGroup(Account::Type type) { Q_D(MyMoneyReport); if (!d->m_accountGroups.isEmpty() && type != Account::Type::Unknown) { if (d->m_accountGroups.contains(type)) return; } d->m_accountGroupFilter = true; if (type != Account::Type::Unknown) d->m_accountGroups.push_back(type); } bool MyMoneyReport::includesAccountGroup(Account::Type type) const { Q_D(const MyMoneyReport); bool result = (! d->m_accountGroupFilter) || (isIncludingTransfers() && d->m_rowType == MyMoneyReport::eExpenseIncome) || d->m_accountGroups.contains(type); return result; } bool MyMoneyReport::includes(const MyMoneyAccount& acc) const { Q_D(const MyMoneyReport); auto result = false; if (includesAccountGroup(acc.accountGroup())) { switch (acc.accountGroup()) { case Account::Type::Income: case Account::Type::Expense: if (isTax()) result = (acc.value("Tax") == "Yes") && includesCategory(acc.id()); else result = includesCategory(acc.id()); break; case Account::Type::Asset: case Account::Type::Liability: if (isLoansOnly()) result = acc.isLoan() && includesAccount(acc.id()); else if (isInvestmentsOnly()) result = acc.isInvest() && includesAccount(acc.id()); else if (isIncludingTransfers() && d->m_rowType == MyMoneyReport::eExpenseIncome) // If transfers are included, ONLY include this account if it is NOT // included in the report itself!! result = ! includesAccount(acc.id()); else result = includesAccount(acc.id()); break; case Account::Type::Equity: if (isInvestmentsOnly()) result = (isIncludingPrice() || isIncludingAveragePrice()) && acc.isInvest() && includesAccount(acc.id()); break; default: result = includesAccount(acc.id()); } } return result; } void MyMoneyReport::write(QDomElement& e, QDomDocument *doc, bool anonymous) const { Q_D(const MyMoneyReport); // No matter what changes, be sure to have a 'type' attribute. Only change // the major type if it becomes impossible to maintain compatibility with // older versions of the program as new features are added to the reports. // Feel free to change the minor type every time a change is made here. // write report's internals if (d->m_reportType == ePivotTable) e.setAttribute(d->getAttrName(Report::Attribute::Type), "pivottable 1.15"); else if (d->m_reportType == eQueryTable) e.setAttribute(d->getAttrName(Report::Attribute::Type), "querytable 1.14"); else if (d->m_reportType == eInfoTable) e.setAttribute(d->getAttrName(Report::Attribute::Type), "infotable 1.0"); e.setAttribute(d->getAttrName(Report::Attribute::Group), d->m_group); - e.setAttribute(d->getAttrName(Report::Attribute::ID), m_id); + e.setAttribute(d->getAttrName(Report::Attribute::ID), d->m_id); // write general tab if (anonymous) { - e.setAttribute(d->getAttrName(Report::Attribute::Name), m_id); + e.setAttribute(d->getAttrName(Report::Attribute::Name), d->m_id); e.setAttribute(d->getAttrName(Report::Attribute::Comment), QString(d->m_comment).fill('x')); } else { e.setAttribute(d->getAttrName(Report::Attribute::Name), d->m_name); e.setAttribute(d->getAttrName(Report::Attribute::Comment), d->m_comment); } e.setAttribute(d->getAttrName(Report::Attribute::ConvertCurrency), d->m_convertCurrency); e.setAttribute(d->getAttrName(Report::Attribute::Favorite), d->m_favorite); e.setAttribute(d->getAttrName(Report::Attribute::SkipZero), d->m_skipZero); e.setAttribute(d->getAttrName(Report::Attribute::DateLock), kDateLockText[(int)d->m_dateLock]); if (d->m_reportType == ePivotTable) { // write report's internals e.setAttribute(d->getAttrName(Report::Attribute::IncludesActuals), d->m_includeBudgetActuals); e.setAttribute(d->getAttrName(Report::Attribute::IncludesForecast), d->m_includeForecast); e.setAttribute(d->getAttrName(Report::Attribute::IncludesPrice), d->m_includePrice); e.setAttribute(d->getAttrName(Report::Attribute::IncludesAveragePrice), d->m_includeAveragePrice); e.setAttribute(d->getAttrName(Report::Attribute::MixedTime), d->m_mixedTime); e.setAttribute(d->getAttrName(Report::Attribute::Investments), d->m_investments); // it's setable in rows/columns tab of querytable, but here it is internal setting // write rows/columns tab if (!d->m_budgetId.isEmpty()) e.setAttribute(d->getAttrName(Report::Attribute::Budget), d->m_budgetId); e.setAttribute(d->getAttrName(Report::Attribute::RowType), kRowTypeText[d->m_rowType]); e.setAttribute(d->getAttrName(Report::Attribute::ShowRowTotals), d->m_showRowTotals); e.setAttribute(d->getAttrName(Report::Attribute::ShowColumnTotals), d->m_showColumnTotals); e.setAttribute(d->getAttrName(Report::Attribute::Detail), kDetailLevelText[d->m_detailLevel]); e.setAttribute(d->getAttrName(Report::Attribute::IncludesMovingAverage), d->m_includeMovingAverage); if (d->m_includeMovingAverage) e.setAttribute(d->getAttrName(Report::Attribute::MovingAverageDays), d->m_movingAverageDays); e.setAttribute(d->getAttrName(Report::Attribute::IncludesSchedules), d->m_includeSchedules); e.setAttribute(d->getAttrName(Report::Attribute::IncludesTransfers), d->m_includeTransfers); e.setAttribute(d->getAttrName(Report::Attribute::IncludesUnused), d->m_includeUnusedAccounts); e.setAttribute(d->getAttrName(Report::Attribute::ColumnsAreDays), d->m_columnsAreDays); // write chart tab if (d->m_chartType < 0 || d->m_chartType >= kChartTypeText.size()) { qDebug("m_chartType out of bounds with %d on report of type %d. Default to none.", d->m_chartType, d->m_reportType); e.setAttribute(d->getAttrName(Report::Attribute::ChartType), kChartTypeText[eChartNone]); } else e.setAttribute(d->getAttrName(Report::Attribute::ChartType), kChartTypeText[d->m_chartType]); e.setAttribute(d->getAttrName(Report::Attribute::ChartCHGridLines), d->m_chartCHGridLines); e.setAttribute(d->getAttrName(Report::Attribute::ChartSVGridLines), d->m_chartSVGridLines); e.setAttribute(d->getAttrName(Report::Attribute::ChartDataLabels), d->m_chartDataLabels); e.setAttribute(d->getAttrName(Report::Attribute::ChartByDefault), d->m_chartByDefault); e.setAttribute(d->getAttrName(Report::Attribute::LogYAxis), d->m_logYaxis); e.setAttribute(d->getAttrName(Report::Attribute::ChartLineWidth), d->m_chartLineWidth); e.setAttribute(d->getAttrName(Report::Attribute::ColumnType), kColumnTypeText[d->m_columnType]); e.setAttribute(d->getAttrName(Report::Attribute::DataLock), kDataLockText[d->m_dataLock]); e.setAttribute(d->getAttrName(Report::Attribute::DataRangeStart), d->m_dataRangeStart); e.setAttribute(d->getAttrName(Report::Attribute::DataRangeEnd), d->m_dataRangeEnd); e.setAttribute(d->getAttrName(Report::Attribute::DataMajorTick), d->m_dataMajorTick); e.setAttribute(d->getAttrName(Report::Attribute::DataMinorTick), d->m_dataMinorTick); e.setAttribute(d->getAttrName(Report::Attribute::YLabelsPrecision), d->m_yLabelsPrecision); } else if (d->m_reportType == eQueryTable) { // write rows/columns tab e.setAttribute(d->getAttrName(Report::Attribute::RowType), kRowTypeText[d->m_rowType]); QStringList columns; unsigned qc = d->m_queryColumns; unsigned it_qc = eQCbegin; unsigned index = 1; while (it_qc != eQCend) { if (qc & it_qc) columns += kQueryColumnsText[index]; it_qc *= 2; index++; } e.setAttribute(d->getAttrName(Report::Attribute::QueryColumns), columns.join(",")); e.setAttribute(d->getAttrName(Report::Attribute::Tax), d->m_tax); e.setAttribute(d->getAttrName(Report::Attribute::Investments), d->m_investments); e.setAttribute(d->getAttrName(Report::Attribute::Loans), d->m_loans); e.setAttribute(d->getAttrName(Report::Attribute::HideTransactions), d->m_hideTransactions); e.setAttribute(d->getAttrName(Report::Attribute::ShowColumnTotals), d->m_showColumnTotals); e.setAttribute(d->getAttrName(Report::Attribute::Detail), kDetailLevelText[d->m_detailLevel]); // write performance tab if (d->m_queryColumns & eQCperformance || d->m_queryColumns & eQCcapitalgain) e.setAttribute(d->getAttrName(Report::Attribute::InvestmentSum), d->m_investmentSum); // write capital gains tab if (d->m_queryColumns & eQCcapitalgain) { if (d->m_investmentSum == MyMoneyReport::eSumSold) { e.setAttribute(d->getAttrName(Report::Attribute::SettlementPeriod), d->m_settlementPeriod); e.setAttribute(d->getAttrName(Report::Attribute::ShowSTLTCapitalGains), d->m_showSTLTCapitalGains); e.setAttribute(d->getAttrName(Report::Attribute::TermsSeparator), d->m_tseparator.toString(Qt::ISODate)); } } } else if (d->m_reportType == eInfoTable) e.setAttribute(d->getAttrName(Report::Attribute::ShowRowTotals), d->m_showRowTotals); // // Text Filter // QRegExp textfilter; if (textFilter(textfilter)) { QDomElement f = doc->createElement(d->getElName(Report::Element::Text)); f.setAttribute(d->getAttrName(Report::Attribute::Pattern), textfilter.pattern()); f.setAttribute(d->getAttrName(Report::Attribute::CaseSensitive), (textfilter.caseSensitivity() == Qt::CaseSensitive) ? 1 : 0); f.setAttribute(d->getAttrName(Report::Attribute::RegEx), (textfilter.patternSyntax() == QRegExp::Wildcard) ? 1 : 0); f.setAttribute(d->getAttrName(Report::Attribute::InvertText), MyMoneyTransactionFilter::isInvertingText()); e.appendChild(f); } // // Type & State Filters // QList typelist; if (types(typelist) && ! typelist.empty()) { // iterate over payees, and add each one QList::const_iterator it_type = typelist.constBegin(); while (it_type != typelist.constEnd()) { QDomElement p = doc->createElement(d->getElName(Report::Element::Type)); p.setAttribute(d->getAttrName(Report::Attribute::Type), kTypeText[*it_type]); e.appendChild(p); ++it_type; } } QList statelist; if (states(statelist) && ! statelist.empty()) { // iterate over payees, and add each one QList::const_iterator it_state = statelist.constBegin(); while (it_state != statelist.constEnd()) { QDomElement p = doc->createElement(d->getElName(Report::Element::State)); p.setAttribute(d->getAttrName(Report::Attribute::State), kStateText[*it_state]); e.appendChild(p); ++it_state; } } // // Number Filter // QString nrFrom, nrTo; if (numberFilter(nrFrom, nrTo)) { QDomElement f = doc->createElement(d->getElName(Report::Element::Number)); f.setAttribute(d->getAttrName(Report::Attribute::From), nrFrom); f.setAttribute(d->getAttrName(Report::Attribute::To), nrTo); e.appendChild(f); } // // Amount Filter // MyMoneyMoney from, to; if (amountFilter(from, to)) { // bool getAmountFilter(MyMoneyMoney&,MyMoneyMoney&); QDomElement f = doc->createElement(d->getElName(Report::Element::Amount)); f.setAttribute(d->getAttrName(Report::Attribute::From), from.toString()); f.setAttribute(d->getAttrName(Report::Attribute::To), to.toString()); e.appendChild(f); } // // Payees Filter // QStringList payeelist; if (payees(payeelist)) { if (payeelist.empty()) { QDomElement p = doc->createElement(d->getElName(Report::Element::Payee)); e.appendChild(p); } else { // iterate over payees, and add each one QStringList::const_iterator it_payee = payeelist.constBegin(); while (it_payee != payeelist.constEnd()) { QDomElement p = doc->createElement(d->getElName(Report::Element::Payee)); p.setAttribute(d->getAttrName(Report::Attribute::ID), *it_payee); e.appendChild(p); ++it_payee; } } } // // Tags Filter // QStringList taglist; if (tags(taglist)) { if (taglist.empty()) { QDomElement p = doc->createElement(d->getElName(Report::Element::Tag)); e.appendChild(p); } else { // iterate over tags, and add each one QStringList::const_iterator it_tag = taglist.constBegin(); while (it_tag != taglist.constEnd()) { QDomElement p = doc->createElement(d->getElName(Report::Element::Tag)); p.setAttribute(d->getAttrName(Report::Attribute::ID), *it_tag); e.appendChild(p); ++it_tag; } } } // // Account Groups Filter // QList accountgrouplist; if (accountGroups(accountgrouplist)) { // iterate over accounts, and add each one QList::const_iterator it_group = accountgrouplist.constBegin(); while (it_group != accountgrouplist.constEnd()) { QDomElement p = doc->createElement(d->getElName(Report::Element::AccountGroup)); p.setAttribute(d->getAttrName(Report::Attribute::Group), kAccountTypeText[(int)*it_group]); e.appendChild(p); ++it_group; } } // // Accounts Filter // QStringList accountlist; if (accounts(accountlist)) { // iterate over accounts, and add each one QStringList::const_iterator it_account = accountlist.constBegin(); while (it_account != accountlist.constEnd()) { QDomElement p = doc->createElement(d->getElName(Report::Element::Account)); p.setAttribute(d->getAttrName(Report::Attribute::ID), *it_account); e.appendChild(p); ++it_account; } } // // Categories Filter // accountlist.clear(); if (categories(accountlist)) { // iterate over accounts, and add each one QStringList::const_iterator it_account = accountlist.constBegin(); while (it_account != accountlist.constEnd()) { QDomElement p = doc->createElement(d->getElName(Report::Element::Category)); p.setAttribute(d->getAttrName(Report::Attribute::ID), *it_account); e.appendChild(p); ++it_account; } } // // Date Filter // if (d->m_dateLock == TransactionFilter::Date::UserDefined) { QDate dateFrom, dateTo; if (dateFilter(dateFrom, dateTo)) { QDomElement f = doc->createElement(d->getElName(Report::Element::Dates)); if (dateFrom.isValid()) f.setAttribute(d->getAttrName(Report::Attribute::From), dateFrom.toString(Qt::ISODate)); if (dateTo.isValid()) f.setAttribute(d->getAttrName(Report::Attribute::To), dateTo.toString(Qt::ISODate)); e.appendChild(f); } } } bool MyMoneyReport::read(const QDomElement& e) { Q_D(MyMoneyReport); // The goal of this reading method is 100% backward AND 100% forward // compatibility. Any report ever created with any version of KMyMoney // should be able to be loaded by this method (as long as it's one of the // report types supported in this version, of course) if (e.tagName().compare(nodeNames[nnReport]) != 0) return false; // read report's internals QString type = e.attribute(d->getAttrName(Report::Attribute::Type)); if (type.startsWith(QLatin1String("pivottable"))) d->m_reportType = ePivotTable; else if (type.startsWith(QLatin1String("querytable"))) d->m_reportType = eQueryTable; else if (type.startsWith(QLatin1String("infotable"))) d->m_reportType = eInfoTable; else return false; d->m_group = e.attribute(d->getAttrName(Report::Attribute::Group)); - m_id = e.attribute(d->getAttrName(Report::Attribute::ID)); + d->m_id = e.attribute(d->getAttrName(Report::Attribute::ID)); clearTransactionFilter(); // read date tab QString datelockstr = e.attribute(d->getAttrName(Report::Attribute::DateLock), "userdefined"); // Handle the pivot 1.2/query 1.1 case where the values were saved as // numbers bool ok = false; int i = datelockstr.toUInt(&ok); if (!ok) { i = kDateLockText.indexOf(datelockstr); if (i == -1) i = (int)TransactionFilter::Date::UserDefined; } setDateFilter(static_cast(i)); // read general tab d->m_name = e.attribute(d->getAttrName(Report::Attribute::Name)); d->m_comment = e.attribute(d->getAttrName(Report::Attribute::Comment), "Extremely old report"); d->m_convertCurrency = e.attribute(d->getAttrName(Report::Attribute::ConvertCurrency), "1").toUInt(); d->m_favorite = e.attribute(d->getAttrName(Report::Attribute::Favorite), "0").toUInt(); d->m_skipZero = e.attribute(d->getAttrName(Report::Attribute::SkipZero), "0").toUInt(); if (d->m_reportType == ePivotTable) { // read report's internals d->m_includeBudgetActuals = e.attribute(d->getAttrName(Report::Attribute::IncludesActuals), "0").toUInt(); d->m_includeForecast = e.attribute(d->getAttrName(Report::Attribute::IncludesForecast), "0").toUInt(); d->m_includePrice = e.attribute(d->getAttrName(Report::Attribute::IncludesPrice), "0").toUInt(); d->m_includeAveragePrice = e.attribute(d->getAttrName(Report::Attribute::IncludesAveragePrice), "0").toUInt(); d->m_mixedTime = e.attribute(d->getAttrName(Report::Attribute::MixedTime), "0").toUInt(); d->m_investments = e.attribute(d->getAttrName(Report::Attribute::Investments), "0").toUInt(); // read rows/columns tab if (e.hasAttribute(d->getAttrName(Report::Attribute::Budget))) d->m_budgetId = e.attribute(d->getAttrName(Report::Attribute::Budget)); i = kRowTypeText.indexOf(e.attribute(d->getAttrName(Report::Attribute::RowType))); if (i != -1) setRowType(static_cast(i)); else setRowType(eExpenseIncome); if (e.hasAttribute(d->getAttrName(Report::Attribute::ShowRowTotals))) d->m_showRowTotals = e.attribute(d->getAttrName(Report::Attribute::ShowRowTotals)).toUInt(); else if (rowType() == eExpenseIncome) // for backward compatibility d->m_showRowTotals = true; d->m_showColumnTotals = e.attribute(d->getAttrName(Report::Attribute::ShowColumnTotals), "1").toUInt(); //check for reports with older settings which didn't have the detail attribute i = kDetailLevelText.indexOf(e.attribute(d->getAttrName(Report::Attribute::Detail))); if (i != -1) d->m_detailLevel = static_cast(i); else d->m_detailLevel = eDetailAll; d->m_includeMovingAverage = e.attribute(d->getAttrName(Report::Attribute::IncludesMovingAverage), "0").toUInt(); if (d->m_includeMovingAverage) d->m_movingAverageDays = e.attribute(d->getAttrName(Report::Attribute::MovingAverageDays), "1").toUInt(); d->m_includeSchedules = e.attribute(d->getAttrName(Report::Attribute::IncludesSchedules), "0").toUInt(); d->m_includeTransfers = e.attribute(d->getAttrName(Report::Attribute::IncludesTransfers), "0").toUInt(); d->m_includeUnusedAccounts = e.attribute(d->getAttrName(Report::Attribute::IncludesUnused), "0").toUInt(); d->m_columnsAreDays = e.attribute(d->getAttrName(Report::Attribute::ColumnsAreDays), "0").toUInt(); // read chart tab i = kChartTypeText.indexOf(e.attribute(d->getAttrName(Report::Attribute::ChartType))); if (i != -1) d->m_chartType = static_cast(i); else d->m_chartType = eChartNone; d->m_chartCHGridLines = e.attribute(d->getAttrName(Report::Attribute::ChartCHGridLines), "1").toUInt(); d->m_chartSVGridLines = e.attribute(d->getAttrName(Report::Attribute::ChartSVGridLines), "1").toUInt(); d->m_chartDataLabels = e.attribute(d->getAttrName(Report::Attribute::ChartDataLabels), "1").toUInt(); d->m_chartByDefault = e.attribute(d->getAttrName(Report::Attribute::ChartByDefault), "0").toUInt(); d->m_logYaxis = e.attribute(d->getAttrName(Report::Attribute::LogYAxis), "0").toUInt(); d->m_chartLineWidth = e.attribute(d->getAttrName(Report::Attribute::ChartLineWidth), QString(m_lineWidth)).toUInt(); // read range tab i = kColumnTypeText.indexOf(e.attribute(d->getAttrName(Report::Attribute::ColumnType))); if (i != -1) setColumnType(static_cast(i)); else setColumnType(eMonths); i = kDataLockText.indexOf(e.attribute(d->getAttrName(Report::Attribute::DataLock))); if (i != -1) setDataFilter(static_cast(i)); else setDataFilter(MyMoneyReport::automatic); d->m_dataRangeStart = e.attribute(d->getAttrName(Report::Attribute::DataRangeStart), "0"); d->m_dataRangeEnd= e.attribute(d->getAttrName(Report::Attribute::DataRangeEnd), "0"); d->m_dataMajorTick = e.attribute(d->getAttrName(Report::Attribute::DataMajorTick), "0"); d->m_dataMinorTick = e.attribute(d->getAttrName(Report::Attribute::DataMinorTick), "0"); d->m_yLabelsPrecision = e.attribute(d->getAttrName(Report::Attribute::YLabelsPrecision), "2").toUInt(); } else if (d->m_reportType == eQueryTable) { // read rows/columns tab i = kRowTypeText.indexOf(e.attribute(d->getAttrName(Report::Attribute::RowType))); if (i != -1) setRowType(static_cast(i)); else setRowType(eAccount); unsigned qc = 0; QStringList columns = e.attribute(d->getAttrName(Report::Attribute::QueryColumns), "none").split(','); foreach (const auto column, columns) { i = kQueryColumnsText.indexOf(column); if (i > 0) qc |= (1 << (i - 1)); } setQueryColumns(static_cast(qc)); d->m_tax = e.attribute(d->getAttrName(Report::Attribute::Tax), "0").toUInt(); d->m_investments = e.attribute(d->getAttrName(Report::Attribute::Investments), "0").toUInt(); d->m_loans = e.attribute(d->getAttrName(Report::Attribute::Loans), "0").toUInt(); d->m_hideTransactions = e.attribute(d->getAttrName(Report::Attribute::HideTransactions), "0").toUInt(); d->m_showColumnTotals = e.attribute(d->getAttrName(Report::Attribute::ShowColumnTotals), "1").toUInt(); d->m_detailLevel = kDetailLevelText.indexOf(e.attribute(d->getAttrName(Report::Attribute::Detail), "none")) == eDetailAll ? eDetailAll : eDetailNone; // read performance or capital gains tab if (d->m_queryColumns & eQCperformance) d->m_investmentSum = static_cast(e.attribute(d->getAttrName(Report::Attribute::InvestmentSum), QString().setNum(MyMoneyReport::eSumPeriod)).toInt()); // read capital gains tab if (d->m_queryColumns & eQCcapitalgain) { d->m_investmentSum = static_cast(e.attribute(d->getAttrName(Report::Attribute::InvestmentSum), QString().setNum(MyMoneyReport::eSumSold)).toInt()); if (d->m_investmentSum == MyMoneyReport::eSumSold) { d->m_showSTLTCapitalGains = e.attribute(d->getAttrName(Report::Attribute::ShowSTLTCapitalGains), "0").toUInt(); d->m_settlementPeriod = e.attribute(d->getAttrName(Report::Attribute::SettlementPeriod), "3").toUInt(); d->m_tseparator = QDate::fromString(e.attribute(d->getAttrName(Report::Attribute::TermsSeparator), QDate::currentDate().addYears(-1).toString(Qt::ISODate)),Qt::ISODate); } } } else if (d->m_reportType == eInfoTable) { if (e.hasAttribute(d->getAttrName(Report::Attribute::ShowRowTotals))) d->m_showRowTotals = e.attribute(d->getAttrName(Report::Attribute::ShowRowTotals)).toUInt(); else d->m_showRowTotals = true; } QDomNode child = e.firstChild(); while (!child.isNull() && child.isElement()) { QDomElement c = child.toElement(); if (d->getElName(Report::Element::Text) == c.tagName() && c.hasAttribute(d->getAttrName(Report::Attribute::Pattern))) { setTextFilter(QRegExp(c.attribute(d->getAttrName(Report::Attribute::Pattern)), c.attribute(d->getAttrName(Report::Attribute::CaseSensitive), "1").toUInt() ? Qt::CaseSensitive : Qt::CaseInsensitive, c.attribute(d->getAttrName(Report::Attribute::RegEx), "1").toUInt() ? QRegExp::Wildcard : QRegExp::RegExp), c.attribute(d->getAttrName(Report::Attribute::InvertText), "0").toUInt()); } if (d->getElName(Report::Element::Type) == c.tagName() && c.hasAttribute(d->getAttrName(Report::Attribute::Type))) { i = kTypeText.indexOf(c.attribute(d->getAttrName(Report::Attribute::Type))); if (i != -1) addType(i); } if (d->getElName(Report::Element::State) == c.tagName() && c.hasAttribute(d->getAttrName(Report::Attribute::State))) { i = kStateText.indexOf(c.attribute(d->getAttrName(Report::Attribute::State))); if (i != -1) addState(i); } if (d->getElName(Report::Element::Number) == c.tagName()) setNumberFilter(c.attribute(d->getAttrName(Report::Attribute::From)), c.attribute(d->getAttrName(Report::Attribute::To))); if (d->getElName(Report::Element::Amount) == c.tagName()) setAmountFilter(MyMoneyMoney(c.attribute(d->getAttrName(Report::Attribute::From), "0/100")), MyMoneyMoney(c.attribute(d->getAttrName(Report::Attribute::To), "0/100"))); if (d->getElName(Report::Element::Dates) == c.tagName()) { QDate from, to; if (c.hasAttribute(d->getAttrName(Report::Attribute::From))) from = QDate::fromString(c.attribute(d->getAttrName(Report::Attribute::From)), Qt::ISODate); if (c.hasAttribute(d->getAttrName(Report::Attribute::To))) to = QDate::fromString(c.attribute(d->getAttrName(Report::Attribute::To)), Qt::ISODate); MyMoneyTransactionFilter::setDateFilter(from, to); } if (d->getElName(Report::Element::Payee) == c.tagName()) addPayee(c.attribute(d->getAttrName(Report::Attribute::ID))); if (d->getElName(Report::Element::Tag) == c.tagName()) addTag(c.attribute(d->getAttrName(Report::Attribute::ID))); if (d->getElName(Report::Element::Category) == c.tagName() && c.hasAttribute(d->getAttrName(Report::Attribute::ID))) addCategory(c.attribute(d->getAttrName(Report::Attribute::ID))); if (d->getElName(Report::Element::Account) == c.tagName() && c.hasAttribute(d->getAttrName(Report::Attribute::ID))) addAccount(c.attribute(d->getAttrName(Report::Attribute::ID))); if (d->getElName(Report::Element::AccountGroup) == c.tagName() && c.hasAttribute(d->getAttrName(Report::Attribute::Group))) { i = kAccountTypeText.indexOf(c.attribute(d->getAttrName(Report::Attribute::Group))); if (i != -1) addAccountGroup(static_cast(i)); } child = child.nextSibling(); } return true; } void MyMoneyReport::writeXML(QDomDocument& document, QDomElement& parent) const { QDomElement el = document.createElement(nodeNames[nnReport]); write(el, &document, false); parent.appendChild(el); } bool MyMoneyReport::hasReferenceTo(const QString& id) const { QStringList list; // collect all ids accounts(list); categories(list); payees(list); tags(list); return (list.contains(id) > 0); } int MyMoneyReport::m_lineWidth = 2; void MyMoneyReport::setLineWidth(int width) { m_lineWidth = width; } QString MyMoneyReport::toString(ERowType type) { switch(type) { case eNoRows : return "eNoRows"; case eAssetLiability : return "eAssetLiability"; case eExpenseIncome : return "eExpenseIncome"; case eCategory : return "eCategory"; case eTopCategory : return "eTopCategory"; case eAccount : return "eAccount"; case eTag : return "eTag"; case ePayee : return "ePayee"; case eMonth : return "eMonth"; case eWeek : return "eWeek"; case eTopAccount : return "eTopAccount"; case eAccountByTopAccount: return "eAccountByTopAccount"; case eEquityType : return "eEquityType"; case eAccountType : return "eAccountType"; case eInstitution : return "eInstitution"; case eBudget : return "eBudget"; case eBudgetActual : return "eBudgetActual"; case eSchedule : return "eSchedule"; case eAccountInfo : return "eAccountInfo"; case eAccountLoanInfo : return "eAccountLoanInfo"; case eAccountReconcile : return "eAccountReconcile"; case eCashFlow : return "eCashFlow"; default : return "undefined"; } } QString MyMoneyReport::toString(MyMoneyReport::EReportType type) { switch(type) { case eNoReport: return "eNoReport"; case ePivotTable: return "ePivotTable"; case eQueryTable: return "eQueryTable"; case eInfoTable: return "eInfoTable"; default: return "undefined"; } } diff --git a/kmymoney/mymoney/mymoneyreport.h b/kmymoney/mymoney/mymoneyreport.h index 189ec9da0..f600d451c 100644 --- a/kmymoney/mymoney/mymoneyreport.h +++ b/kmymoney/mymoney/mymoneyreport.h @@ -1,493 +1,491 @@ /*************************************************************************** mymoneyreport.h ------------------- begin : Sun July 4 2004 copyright : (C) 2004-2005 by Ace Jones email : acejones@users.sourceforge.net (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 MYMONEYREPORT_H #define MYMONEYREPORT_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyobject.h" #include "mymoneytransactionfilter.h" #include "kmm_mymoney_export.h" #include "mymoneyunittestable.h" class QString; class QDomElement; class QDomDocument; class MyMoneyAccount; template class QList; namespace eMyMoney { namespace Account { enum class Type; } namespace TransactionFilter { enum class Date; } } /** * This class defines a report within the MyMoneyEngine. The report class * contains all the configuration parameters needed to run a report, plus * XML serialization. * * A report is a transactionfilter, so any report can specify which * transactions it's interested down to the most minute level of detail. * It extends the transactionfilter by providing identification (name, * comments, group type, etc) as well as layout information (what kind * of layout should be used, how the rows & columns should be presented, * currency converted, etc.) * * As noted above, this class only provides a report DEFINITION. The * generation and presentation of the report itself are left to higher * level classes. * * @author Ace Jones */ class MyMoneyReportPrivate; class KMM_MYMONEY_EXPORT MyMoneyReport: public MyMoneyObject, public MyMoneyTransactionFilter { - Q_DECLARE_PRIVATE(MyMoneyReport) - MyMoneyReportPrivate* d_ptr; + Q_DECLARE_PRIVATE_D(MyMoneyObject::d_ptr, MyMoneyReport) KMM_MYMONEY_UNIT_TESTABLE //protected: // MyMoneyReport(MyMoneyReportPrivate &dd); public: // When adding a new row type, be sure to add a corresponding entry in kTypeArray enum ERowType { eNoRows = 0, eAssetLiability, eExpenseIncome, eCategory, eTopCategory, eAccount, eTag, ePayee, eMonth, eWeek, eTopAccount, eAccountByTopAccount, eEquityType, eAccountType, eInstitution, eBudget, eBudgetActual, eSchedule, eAccountInfo, eAccountLoanInfo, eAccountReconcile, eCashFlow}; enum EReportType { eNoReport = 0, ePivotTable, eQueryTable, eInfoTable }; enum EColumnType { eNoColumns = 0, eDays = 1, eMonths = 1, eBiMonths = 2, eQuarters = 3, eWeeks = 7, eYears = 12 }; // if you add bits to this bitmask, start with the value currently assigned to eQCend and update its value afterwards // also don't forget to add column names to kQueryColumnsText in mymoneyreport.cpp enum EQueryColumns { eQCnone = 0x0, eQCbegin = 0x1, eQCnumber = 0x1, eQCpayee = 0x2, eQCcategory = 0x4, eQCtag = 0x8, eQCmemo = 0x10, eQCaccount = 0x20, eQCreconciled = 0x40, eQCaction = 0x80, eQCshares = 0x100, eQCprice = 0x200, eQCperformance = 0x400, eQCloan = 0x800, eQCbalance = 0x1000, eQCcapitalgain = 0x2000, eQCend = 0x4000 }; enum EDetailLevel { eDetailNone = 0, eDetailAll, eDetailTop, eDetailGroup, eDetailTotal, eDetailEnd }; enum EInvestmentSum { eSumPeriod = 0, eSumOwnedAndSold, eSumOwned, eSumSold, eSumBought}; enum EChartType { eChartNone = 0, eChartLine, eChartBar, eChartPie, eChartRing, eChartStackedBar, eChartEnd }; enum dataOptionE { automatic = 0, userDefined, dataOptionCount }; static const QStringList kRowTypeText; static const QStringList kColumnTypeText; static const QStringList kQueryColumnsText; static const QStringList kDetailLevelText; static const QStringList kChartTypeText; static const EReportType kTypeArray[]; public: MyMoneyReport(); explicit MyMoneyReport(ERowType rt, unsigned ct, eMyMoney::TransactionFilter::Date dl, EDetailLevel ss, const QString& name, const QString& comment); /** * This constructor creates an object based on the data found in the * QDomElement referenced by @p node. If problems arise, the @p id of * the object is cleared (see MyMoneyObject::clearId()). */ explicit MyMoneyReport(const QDomElement& node); MyMoneyReport(const QString& id, const MyMoneyReport& other); MyMoneyReport(const MyMoneyReport & other); MyMoneyReport(MyMoneyReport && other); MyMoneyReport & operator=(MyMoneyReport other); friend void swap(MyMoneyReport& first, MyMoneyReport& second); ~MyMoneyReport(); EReportType reportType() const; QString name() const; void setName(const QString& s); bool isShowingRowTotals() const; void setShowingRowTotals(bool f); bool isShowingColumnTotals() const; void setShowingColumnTotals(bool f); ERowType rowType() const; void setRowType(ERowType rt); bool isRunningSum() const; EColumnType columnType() const; void setColumnType(EColumnType ct); bool isConvertCurrency() const; void setConvertCurrency(bool f); uint columnPitch() const; QString comment() const; void setComment(const QString& comment); EQueryColumns queryColumns() const; void setQueryColumns(EQueryColumns qc); QString group() const; void setGroup(const QString& group); bool isFavorite() const; void setFavorite(bool f); bool isTax() const; void setTax(bool f); bool isInvestmentsOnly() const; void setInvestmentsOnly(bool f); bool isLoansOnly() const; void setLoansOnly(bool f); EDetailLevel detailLevel() const; void setDetailLevel(EDetailLevel detail); EInvestmentSum investmentSum() const; void setInvestmentSum(EInvestmentSum sum); bool isHideTransactions() const; void setHideTransactions(bool f); EChartType chartType() const; void setChartType(EChartType type); bool isChartDataLabels() const; void setChartDataLabels(bool f); bool isChartCHGridLines() const; void setChartCHGridLines(bool f); bool isChartSVGridLines() const; void setChartSVGridLines(bool f); bool isChartByDefault() const; void setChartByDefault(bool f); uint chartLineWidth() const; void setChartLineWidth(uint f); bool isLogYAxis() const; void setLogYAxis(bool f); QString dataRangeStart() const; void setDataRangeStart(const QString& f); QString dataRangeEnd() const; void setDataRangeEnd(const QString& f); QString dataMajorTick() const; void setDataMajorTick(const QString& f); QString dataMinorTick() const; void setDataMinorTick(const QString& f); uint yLabelsPrecision() const; void setYLabelsPrecision(int f); bool isIncludingSchedules() const; void setIncludingSchedules(bool f); bool isColumnsAreDays() const; void setColumnsAreDays(bool f); bool isIncludingTransfers() const; void setIncludingTransfers(bool f); bool isIncludingUnusedAccounts() const; void setIncludingUnusedAccounts(bool f); bool hasBudget() const; QString budget() const; /** * Sets the budget used for this report * * @param budget The ID of the budget to use, or an empty string * to indicate a budget is NOT included * @param fa Whether to display actual data alongside the budget. * Setting to false means the report displays ONLY the budget itself. * @warning For now, the budget ID is ignored. The budget id is * simply checked for any non-empty string, and if so, hasBudget() * will return true. */ void setBudget(const QString& budget, bool fa = true); bool isIncludingBudgetActuals() const; void setIncludingBudgetActuals(bool f); bool isIncludingForecast() const; void setIncludingForecast(bool f); bool isIncludingMovingAverage() const; void setIncludingMovingAverage(bool f); int movingAverageDays() const; void setMovingAverageDays(int days); bool isIncludingPrice() const; void setIncludingPrice(bool f); bool isIncludingAveragePrice() const; void setIncludingAveragePrice(bool f); dataOptionE dataFilter() const; bool isDataUserDefined() const; void setDataFilter(dataOptionE u); eMyMoney::TransactionFilter::Date dateRange() const; bool isDateUserDefined() const; /** * Set the underlying date filter and LOCK that filter to the specified * range. For example, if @p _u is "CurrentMonth", this report should always * be updated to the current month no matter when the report is run. * * This updating is not entirely automatic, you should update it yourself by * calling updateDateFilter. * * @param _u The date range constant (MyMoneyTransactionFilter::dateRangeE) * which this report should be locked to. */ void setDateFilter(eMyMoney::TransactionFilter::Date u); /** * Set the underlying date filter using the start and end dates provided. * Note that this does not LOCK to any range like setDateFilter(unsigned) * above. It is just a reimplementation of the MyMoneyTransactionFilter * version. * * @param _db The inclusive begin date of the date range * @param _de The inclusive end date of the date range */ void setDateFilter(const QDate& db, const QDate& de); /** * Set the underlying date filter using the 'date lock' property. * * Always call this function before executing the report to be sure that * the date filters properly match the plain-language 'date lock'. * * For example, if the report is date-locked to "Current Month", and the * last time you loaded or ran the report was in August, but it's now * September, this function will update the date range to be September, * as is proper. */ void updateDateFilter(); bool isMixedTime() const; void setMixedTime(bool f); int currentDateColumn() const; void setCurrentDateColumn(int f); uint settlementPeriod() const; void setSettlementPeriod(uint days); bool isShowingSTLTCapitalGains() const; void setShowSTLTCapitalGains(bool f); QDate termSeparator() const; void setTermSeparator(const QDate& date); bool isSkippingZero() const; void setSkipZero(int f); /** * This method allows you to clear the underlying transaction filter */ void clearTransactionFilter(); /** * This method allows you to set the underlying transaction filter * * @param _filter The filter which should replace the existing transaction * filter. */ void assignFilter(const MyMoneyTransactionFilter& filter); /** * Retrieves a VALID beginning & ending date for this report. * * The underlying date filter can return en empty QDate() for either the * begin or end date or both. This is typically unacceptable for reports, * which need the REAL begin and end date. * * This function gets the underlying date filter range, and if either is * an empty QDate(), it determines the missing date from looking at all * the transactions which match the underlying filter, and returning the * date of the first or last transaction (as appropriate). * * @param _db The inclusive begin date of the date range * @param _de The inclusive end date of the date range */ void validDateRange(QDate &db, QDate &de); /** * This method turns on the account group filter and adds the * @p type to the list of allowed groups. * * Note that account group filtering is handled differently * than all the filters of the underlying class. This filter * is meant to be applied to individual splits of matched * transactions AFTER the underlying filter is used to find * the matching transactions. * * @param type the account group to add to the allowed groups list */ void addAccountGroup(eMyMoney::Account::Type type); /** * This method returns whether an account group filter has been set, * and if so, it returns all the account groups set in the filter. * * @param list list to append account groups into * @return return true if an account group filter has been set */ bool accountGroups(QList& list) const; /** * This method returns whether the specified account group * is allowed by the account groups filter. * * @param type group to append account groups into * @return return true if an account group filter has been set */ bool includesAccountGroup(eMyMoney::Account::Type type) const; /** * This method is used to test whether a specific account * passes the accountGroup test and either the Account or * Category test, depending on which sort of Account it is. * * The m_tax and m_investments properties are also considered. * * @param acc the account in question * @return true if account is in filter set, false otherwise */ bool includes(const MyMoneyAccount& acc) const; /** * This method writes this report to the DOM element @p e, * within the DOM document @p doc. * * @param e The element which should be populated with info from this report * @param doc The document which we can use to create new sub-elements * if needed * @param anonymous Whether the sensitive parts of the report should be * masked */ void write(QDomElement& e, QDomDocument *doc, bool anonymous = false) const; /** * This method reads a report from the DOM element @p e, and * populates this report with the results. * * @param e The element from which the report should be read * * @return bool True if a report was successfully loaded from the * element @p e. If false is returned, the contents of this report * object are undefined. */ bool read(const QDomElement& e); /** * This method creates a QDomElement for the @p document * under the parent node @p parent. (This version overwrites the * MMObject base class.) * * @param document reference to QDomDocument * @param parent reference to QDomElement parent node */ void writeXML(QDomDocument& document, QDomElement& parent) const override; /** * This method checks if a reference to the given object exists. It returns, * a @p true if the object is referencing the one requested by the * parameter @p id. If it does not, this method returns @p false. * * @param id id of the object to be checked for references * @retval true This object references object with id @p id. * @retval false This object does not reference the object with id @p id. */ bool hasReferenceTo(const QString& id) const override; /** * This method allows to modify the default lineWidth for graphs. * The default is 2. */ static void setLineWidth(int width); /** * This member keeps the current setting for line graphs lineWidth. * @sa setLineWidth() */ static int m_lineWidth; /** * Return row type as string. * * @param type type to get string for * @return row type converted to string */ static QString toString(ERowType type); /** * Return report type as string. * * @param type report type to get string for * @return report type converted to string */ static QString toString(EReportType type); }; inline void swap(MyMoneyReport& first, MyMoneyReport& second) // krazy:exclude=inline { using std::swap; - swap(first.d_ptr, second.d_ptr); - swap(first.m_id, second.m_id); + swap(first.MyMoneyObject::d_ptr, second.MyMoneyObject::d_ptr); swap(first.MyMoneyTransactionFilter::d_ptr, second.MyMoneyTransactionFilter::d_ptr); } inline MyMoneyReport::MyMoneyReport(MyMoneyReport && other) : MyMoneyReport() // krazy:exclude=inline { swap(*this, other); } inline MyMoneyReport & MyMoneyReport::operator=(MyMoneyReport other) // krazy:exclude=inline { swap(*this, other); return *this; } /** * Make it possible to hold @ref MyMoneyReport objects inside @ref QVariant objects. */ Q_DECLARE_METATYPE(MyMoneyReport) #endif // MYMONEYREPORT_H diff --git a/kmymoney/mymoney/mymoneyreport_p.h b/kmymoney/mymoney/mymoneyreport_p.h index 4bfed462c..be4a6c6fb 100644 --- a/kmymoney/mymoney/mymoneyreport_p.h +++ b/kmymoney/mymoney/mymoneyreport_p.h @@ -1,411 +1,412 @@ /*************************************************************************** mymoneyreport.cpp ------------------- begin : Sun July 4 2004 copyright : (C) 2004-2005 by Ace Jones email : acejones@users.sourceforge.net (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 MYMONEYREPORT_P_H #define MYMONEYREPORT_P_H #include "mymoneyreport.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes +#include "mymoneyobject_p.h" #include "mymoneyenums.h" using namespace eMyMoney; namespace Report { enum class Element { Payee, Tag, Account, Text, Type, State, Number, Amount, Dates, Category, AccountGroup }; uint qHash(const Element key, uint seed) { return ::qHash(static_cast(key), seed); } enum class Attribute { ID, Group, Type, Name, Comment, ConvertCurrency, Favorite, SkipZero, DateLock, DataLock, MovingAverageDays, IncludesActuals, IncludesForecast, IncludesPrice, IncludesAveragePrice, IncludesMovingAverage, IncludesSchedules, IncludesTransfers, IncludesUnused, MixedTime, Investments, Budget, ShowRowTotals, ShowColumnTotals, Detail, ColumnsAreDays, ChartType, ChartCHGridLines, ChartSVGridLines, ChartDataLabels, ChartByDefault, LogYAxis, ChartLineWidth, ColumnType, RowType, DataRangeStart, DataRangeEnd, DataMajorTick, DataMinorTick, YLabelsPrecision, QueryColumns, Tax, Loans, HideTransactions, InvestmentSum, SettlementPeriod, ShowSTLTCapitalGains, TermsSeparator, Pattern, CaseSensitive, RegEx, InvertText, State, From, To, // insert new entries above this line LastAttribute }; uint qHash(const Attribute key, uint seed) { return ::qHash(static_cast(key), seed); } } -class MyMoneyReportPrivate { - +class MyMoneyReportPrivate : public MyMoneyObjectPrivate +{ public: static QString getElName(const Report::Element el) { static const QHash elNames { {Report::Element::Payee, QStringLiteral("PAYEE")}, {Report::Element::Tag, QStringLiteral("TAG")}, {Report::Element::Account, QStringLiteral("ACCOUNT")}, {Report::Element::Text, QStringLiteral("TEXT")}, {Report::Element::Type, QStringLiteral("TYPE")}, {Report::Element::State, QStringLiteral("STATE")}, {Report::Element::Number, QStringLiteral("NUMBER")}, {Report::Element::Amount, QStringLiteral("AMOUNT")}, {Report::Element::Dates, QStringLiteral("DATES")}, {Report::Element::Category, QStringLiteral("CATEGORY")}, {Report::Element::AccountGroup, QStringLiteral("ACCOUNTGROUP")} }; return elNames[el]; } static QString getAttrName(const Report::Attribute attr) { static const QHash attrNames { {Report::Attribute::ID, QStringLiteral("id")}, {Report::Attribute::Group, QStringLiteral("group")}, {Report::Attribute::Type, QStringLiteral("type")}, {Report::Attribute::Name, QStringLiteral("name")}, {Report::Attribute::Comment, QStringLiteral("comment")}, {Report::Attribute::ConvertCurrency, QStringLiteral("convertcurrency")}, {Report::Attribute::Favorite, QStringLiteral("favorite")}, {Report::Attribute::SkipZero, QStringLiteral("skipZero")}, {Report::Attribute::DateLock, QStringLiteral("datelock")}, {Report::Attribute::DataLock, QStringLiteral("datalock")}, {Report::Attribute::MovingAverageDays, QStringLiteral("movingaveragedays")}, {Report::Attribute::IncludesActuals, QStringLiteral("includesactuals")}, {Report::Attribute::IncludesForecast, QStringLiteral("includesforecast")}, {Report::Attribute::IncludesPrice, QStringLiteral("includesprice")}, {Report::Attribute::IncludesAveragePrice, QStringLiteral("includesaverageprice")}, {Report::Attribute::IncludesMovingAverage, QStringLiteral("includesmovingaverage")}, {Report::Attribute::IncludesSchedules, QStringLiteral("includeschedules")}, {Report::Attribute::IncludesTransfers, QStringLiteral("includestransfers")}, {Report::Attribute::IncludesUnused, QStringLiteral("includeunused")}, {Report::Attribute::MixedTime, QStringLiteral("mixedtime")}, {Report::Attribute::Investments, QStringLiteral("investments")}, {Report::Attribute::Budget, QStringLiteral("budget")}, {Report::Attribute::ShowRowTotals, QStringLiteral("showrowtotals")}, {Report::Attribute::ShowColumnTotals, QStringLiteral("showcolumntotals")}, {Report::Attribute::Detail, QStringLiteral("detail")}, {Report::Attribute::ColumnsAreDays, QStringLiteral("columnsaredays")}, {Report::Attribute::ChartType, QStringLiteral("charttype")}, {Report::Attribute::ChartCHGridLines, QStringLiteral("chartchgridlines")}, {Report::Attribute::ChartSVGridLines, QStringLiteral("chartsvgridlines")}, {Report::Attribute::ChartDataLabels, QStringLiteral("chartdatalabels")}, {Report::Attribute::ChartByDefault, QStringLiteral("chartbydefault")}, {Report::Attribute::LogYAxis, QStringLiteral("logYaxis")}, {Report::Attribute::ChartLineWidth, QStringLiteral("chartlinewidth")}, {Report::Attribute::ColumnType, QStringLiteral("columntype")}, {Report::Attribute::RowType, QStringLiteral("rowtype")}, {Report::Attribute::DataRangeStart, QStringLiteral("dataRangeStart")}, {Report::Attribute::DataRangeEnd, QStringLiteral("dataRangeEnd")}, {Report::Attribute::DataMajorTick, QStringLiteral("dataMajorTick")}, {Report::Attribute::DataMinorTick, QStringLiteral("dataMinorTick")}, {Report::Attribute::YLabelsPrecision, QStringLiteral("yLabelsPrecision")}, {Report::Attribute::QueryColumns, QStringLiteral("querycolumns")}, {Report::Attribute::Tax, QStringLiteral("tax")}, {Report::Attribute::Loans, QStringLiteral("loans")}, {Report::Attribute::HideTransactions, QStringLiteral("hidetransactions")}, {Report::Attribute::InvestmentSum, QStringLiteral("investmentsum")}, {Report::Attribute::SettlementPeriod, QStringLiteral("settlementperiod")}, {Report::Attribute::ShowSTLTCapitalGains, QStringLiteral("showSTLTCapitalGains")}, {Report::Attribute::TermsSeparator, QStringLiteral("tseparator")}, {Report::Attribute::Pattern, QStringLiteral("pattern")}, {Report::Attribute::CaseSensitive, QStringLiteral("casesensitive")}, {Report::Attribute::RegEx, QStringLiteral("regex")}, {Report::Attribute::InvertText, QStringLiteral("inverttext")}, {Report::Attribute::State, QStringLiteral("state")}, {Report::Attribute::From, QStringLiteral("from")}, {Report::Attribute::To, QStringLiteral("to")} }; return attrNames[attr]; } /** * The user-assigned name of the report */ QString m_name; /** * The user-assigned comment for the report, in case they want to make * additional notes for themselves about the report. */ QString m_comment; /** * Where to group this report amongst the others in the UI view. This * should be assigned by the UI system. */ QString m_group; /** * How much detail to show in the accounts */ MyMoneyReport::EDetailLevel m_detailLevel; /** * Whether to sum: all, sold, bought or owned value */ MyMoneyReport::EInvestmentSum m_investmentSum; /** * Whether to show transactions or just totals. */ bool m_hideTransactions; /** * Whether to convert all currencies to the base currency of the file (true). * If this is false, it's up to the report generator to decide how to handle * the currency. */ bool m_convertCurrency; /** * Whether this is one of the users' favorite reports */ bool m_favorite; /** * Whether this report should only include categories marked as "Tax"="Yes" */ bool m_tax; /** * Whether this report should only include investment accounts */ bool m_investments; /** * Whether this report should only include loan accounts * Applies only to querytable reports. Mutually exclusive with * m_investments. */ bool m_loans; /** * What sort of algorithm should be used to run the report */ MyMoneyReport::EReportType m_reportType; /** * What sort of values should show up on the ROWS of this report */ MyMoneyReport::ERowType m_rowType; /** * What sort of values should show up on the COLUMNS of this report, * in the case of a 'PivotTable' report. Really this is used more as a * QUANTITY of months or days. Whether it's months or days is determined * by m_columnsAreDays. */ MyMoneyReport::EColumnType m_columnType; /** * Whether the base unit of columns of this report is days. Only applies to * 'PivotTable' reports. If false, then columns are months or multiples thereof. */ bool m_columnsAreDays; /** * What sort of values should show up on the COLUMNS of this report, * in the case of a 'QueryTable' report */ MyMoneyReport::EQueryColumns m_queryColumns; /** * The plain-language description of what the date range should be locked * to. 'userDefined' means NO locking, in any other case, the report * will be adjusted to match the date lock. So if the date lock is * 'currentMonth', the start and end dates of the underlying filter will * be updated to whatever the current month is. This updating happens * automatically when the report is loaded, and should also be done * manually by calling updateDateFilter() before generating the report */ eMyMoney::TransactionFilter::Date m_dateLock; /** * Which account groups should be included in the report. This filter * is applied to the individual splits AFTER a transaction has been * matched using the underlying filter. */ QList m_accountGroups; /** * Whether an account group filter has been set (see m_accountGroups) */ bool m_accountGroupFilter; /** * What format should be used to draw this report as a chart */ MyMoneyReport::EChartType m_chartType; /** * Whether the value of individual data points should be drawn on the chart */ bool m_chartDataLabels; /** * Whether grid lines should be drawn on the chart */ bool m_chartCHGridLines; bool m_chartSVGridLines; /** * Whether this report should be shown as a chart by default (otherwise it * should be shown as a textual report) */ bool m_chartByDefault; /** * Width of the chart lines */ uint m_chartLineWidth; /** * Whether Y axis is logarithmic or linear */ bool m_logYaxis; /** * Y data range */ QString m_dataRangeStart; QString m_dataRangeEnd; /** * Y data range division */ QString m_dataMajorTick; QString m_dataMinorTick; /** * Y labels precision */ uint m_yLabelsPrecision; /** * Whether data range should be calculated automatically or is user defined */ MyMoneyReport::dataOptionE m_dataLock; /** * Whether to include scheduled transactions */ bool m_includeSchedules; /** * Whether to include transfers. Only applies to Income/Expense reports */ bool m_includeTransfers; /** * The id of the budget associated with this report. */ QString m_budgetId; /** * Whether this report should print the actual data to go along with * the budget. This is only valid if the report has a budget. */ bool m_includeBudgetActuals; /** * Whether this report should include all accounts and not only * accounts with transactions. */ bool m_includeUnusedAccounts; /** * Whether this report should include columns for row totals */ bool m_showRowTotals; /** * Whether this report should include rows for column totals */ bool m_showColumnTotals; /** * Whether this report should include forecast balance */ bool m_includeForecast; /** * Whether this report should include moving average */ bool m_includeMovingAverage; /** * The amount of days that spans each moving average */ int m_movingAverageDays; /** * Whether this report should include prices */ bool m_includePrice; /** * Whether this report should include moving average prices */ bool m_includeAveragePrice; /** * Make the actual and forecast lines display as one */ bool m_mixedTime; /** * This stores the column for the current date * This value is calculated dinamically and thus it is not saved in the file */ int m_currentDateColumn; /** * Time in days between the settlement date and the transaction date. */ uint m_settlementPeriod; /** * Controls showing short-term and long-term capital gains. */ bool m_showSTLTCapitalGains; /** * Date separating shot-term from long-term gains. */ QDate m_tseparator; /** * This option is for investments reports only which * show prices instead of balances as all other reports do. *

* Select this option to include prices for the given period (week, month, * quarter, ...) only. *

*

* If this option is off the last existing price is shown for a period, if * it is on, in a table the value is '0' shown and in a chart a linear * interpolation for the missing values will be performed. *
Example: *
There are prices for January and March, but there is no price for * February. *

    *
  • OFF: shows the price for February as the last price of * January *
  • ON: in a table the value is '0', in a chart a linear * interpolation for the February-price will be performed * (so it makes a kind of average-value using the January- and the * March-price in the chart) *
*

*/ bool m_skipZero; }; #endif diff --git a/kmymoney/mymoney/mymoneyschedule.cpp b/kmymoney/mymoney/mymoneyschedule.cpp index 62feea881..2720d9fe4 100644 --- a/kmymoney/mymoney/mymoneyschedule.cpp +++ b/kmymoney/mymoney/mymoneyschedule.cpp @@ -1,1557 +1,1550 @@ /*************************************************************************** mymoneyschedule.cpp ------------------- copyright : (C) 2000-2002 by Michael Edwardes (C) 2007 by Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneyschedule.h" #include "mymoneyschedule_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyutils.h" #include "mymoneyexception.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneysplit.h" #include "imymoneyprocessingcalendar.h" #include "mymoneystoragenames.h" using namespace MyMoneyStorageNodes; using namespace eMyMoney; static IMyMoneyProcessingCalendar* processingCalendarPtr = 0; MyMoneySchedule::MyMoneySchedule() : - MyMoneyObject(), - d_ptr(new MyMoneySchedulePrivate) + MyMoneyObject(*new MyMoneySchedulePrivate) { } MyMoneySchedule::MyMoneySchedule(const QString& name, Schedule::Type type, Schedule::Occurrence occurrence, int occurrenceMultiplier, Schedule::PaymentType paymentType, const QDate& /* startDate */, const QDate& endDate, bool fixed, bool autoEnter) : - MyMoneyObject(), - d_ptr(new MyMoneySchedulePrivate) + MyMoneyObject(*new MyMoneySchedulePrivate) { Q_D(MyMoneySchedule); // Set up the values possibly differeing from defaults d->m_name = name; d->m_occurrence = occurrence; d->m_occurrenceMultiplier = occurrenceMultiplier; simpleToCompoundOccurrence(d->m_occurrenceMultiplier, d->m_occurrence); d->m_type = type; d->m_paymentType = paymentType; d->m_fixed = fixed; d->m_autoEnter = autoEnter; d->m_endDate = endDate; } MyMoneySchedule::MyMoneySchedule(const QDomElement& node) : - MyMoneyObject(node), - d_ptr(new MyMoneySchedulePrivate) + MyMoneyObject(*new MyMoneySchedulePrivate, node) { if (nodeNames[nnScheduleTX] != node.tagName()) throw MYMONEYEXCEPTION("Node was not SCHEDULED_TX"); Q_D(MyMoneySchedule); d->m_name = node.attribute(d->getAttrName(Schedule::Attribute::Name)); d->m_startDate = MyMoneyUtils::stringToDate(node.attribute(d->getAttrName(Schedule::Attribute::StartDate))); d->m_endDate = MyMoneyUtils::stringToDate(node.attribute(d->getAttrName(Schedule::Attribute::EndDate))); d->m_lastPayment = MyMoneyUtils::stringToDate(node.attribute(d->getAttrName(Schedule::Attribute::LastPayment))); d->m_type = static_cast(node.attribute(d->getAttrName(Schedule::Attribute::Type)).toInt()); d->m_paymentType = static_cast(node.attribute(d->getAttrName(Schedule::Attribute::PaymentType)).toInt()); d->m_occurrence = static_cast(node.attribute(d->getAttrName(Schedule::Attribute::Occurrence)).toInt()); d->m_occurrenceMultiplier = node.attribute(d->getAttrName(Schedule::Attribute::OccurrenceMultiplier), "1").toInt(); // Convert to compound occurrence simpleToCompoundOccurrence(d->m_occurrenceMultiplier, d->m_occurrence); d->m_lastDayInMonth = static_cast(node.attribute("lastDayInMonth").toInt()); d->m_autoEnter = static_cast(node.attribute(d->getAttrName(Schedule::Attribute::AutoEnter)).toInt()); d->m_fixed = static_cast(node.attribute(d->getAttrName(Schedule::Attribute::Fixed)).toInt()); d->m_weekendOption = static_cast(node.attribute(d->getAttrName(Schedule::Attribute::WeekendOption)).toInt()); // read in the associated transaction QDomNodeList nodeList = node.elementsByTagName(nodeNames[nnTransaction]); if (nodeList.count() == 0) throw MYMONEYEXCEPTION("SCHEDULED_TX has no TRANSACTION node"); setTransaction(MyMoneyTransaction(nodeList.item(0).toElement(), false), true); // some old versions did not remove the entry date and post date fields // in the schedule. So if this is the case, we deal with a very old transaction // and can't use the post date field as next due date. Hence, we wipe it out here if (d->m_transaction.entryDate().isValid()) { d->m_transaction.setPostDate(QDate()); d->m_transaction.setEntryDate(QDate()); } // readin the recorded payments nodeList = node.elementsByTagName(d->getElName(Schedule::Element::Payments)); if (nodeList.count() > 0) { nodeList = nodeList.item(0).toElement().elementsByTagName(d->getElName(Schedule::Element::Payment)); for (int i = 0; i < nodeList.count(); ++i) { d->m_recordedPayments << MyMoneyUtils::stringToDate(nodeList.item(i).toElement().attribute(d->getAttrName(Schedule::Attribute::Date))); } } // if the next due date is not set (comes from old version) // then set it up the old way if (!nextDueDate().isValid() && !d->m_lastPayment.isValid()) { d->m_transaction.setPostDate(d->m_startDate); // clear it, because the schedule has never been used d->m_startDate = QDate(); } // There are reports that lastPayment and nextDueDate are identical or // that nextDueDate is older than lastPayment. This could // be caused by older versions of the application. In this case, we just // clear out the nextDueDate and let it calculate from the lastPayment. if (nextDueDate().isValid() && nextDueDate() <= d->m_lastPayment) { d->m_transaction.setPostDate(QDate()); } if (!nextDueDate().isValid()) { d->m_transaction.setPostDate(d->m_startDate); d->m_transaction.setPostDate(nextPayment(d->m_lastPayment.addDays(1))); } } MyMoneySchedule::MyMoneySchedule(const MyMoneySchedule& other) : - MyMoneyObject(other.id()), - d_ptr(new MyMoneySchedulePrivate(*other.d_func())) + MyMoneyObject(*new MyMoneySchedulePrivate(*other.d_func()), other.id()) { } MyMoneySchedule::MyMoneySchedule(const QString& id, const MyMoneySchedule& other) : - MyMoneyObject(id), - d_ptr(new MyMoneySchedulePrivate(*other.d_func())) + MyMoneyObject(*new MyMoneySchedulePrivate(*other.d_func()), id) { } MyMoneySchedule::~MyMoneySchedule() { - Q_D(MyMoneySchedule); - delete d; } Schedule::Occurrence MyMoneySchedule::occurrence() const { Q_D(const MyMoneySchedule); Schedule::Occurrence occ = d->m_occurrence; int mult = d->m_occurrenceMultiplier; compoundToSimpleOccurrence(mult, occ); return occ; } int MyMoneySchedule::occurrenceMultiplier() const { Q_D(const MyMoneySchedule); return d->m_occurrenceMultiplier; } eMyMoney::Schedule::Type MyMoneySchedule::type() const { Q_D(const MyMoneySchedule); return d->m_type; } eMyMoney::Schedule::Occurrence MyMoneySchedule::occurrencePeriod() const { Q_D(const MyMoneySchedule); return d->m_occurrence; } void MyMoneySchedule::setStartDate(const QDate& date) { Q_D(MyMoneySchedule); d->m_startDate = date; } void MyMoneySchedule::setPaymentType(Schedule::PaymentType type) { Q_D(MyMoneySchedule); d->m_paymentType = type; } void MyMoneySchedule::setFixed(bool fixed) { Q_D(MyMoneySchedule); d->m_fixed = fixed; } void MyMoneySchedule::setTransaction(const MyMoneyTransaction& transaction) { setTransaction(transaction, false); } void MyMoneySchedule::setTransaction(const MyMoneyTransaction& transaction, bool noDateCheck) { auto t = transaction; Q_D(MyMoneySchedule); if (!noDateCheck) { // don't allow a transaction that has no due date // if we get something like that, then we use the // the current next due date. If that is also invalid // we can't help it. if (!t.postDate().isValid()) { t.setPostDate(d->m_transaction.postDate()); } if (!t.postDate().isValid()) return; } // make sure to clear out some unused information in scheduled transactions // we need to do this for the case that the transaction passed as argument // is a matched or imported transaction. QList splits = t.splits(); if (splits.count() > 0) { QList::const_iterator it_s; for (it_s = splits.constBegin(); it_s != splits.constEnd(); ++it_s) { MyMoneySplit s = *it_s; // clear out the bankID if (!(*it_s).bankID().isEmpty()) { s.setBankID(QString()); t.modifySplit(s); } // only clear payees from second split onwards if (it_s == splits.constBegin()) continue; if (!(*it_s).payeeId().isEmpty()) { // but only if the split references an income/expense category MyMoneyFile* file = MyMoneyFile::instance(); // some unit tests don't have a storage attached, so we // simply skip the test // Don't check for accounts with an id of 'Phony-ID' which is used // internally for non-existing accounts (during creation of accounts) if (file->storageAttached() && s.accountId() != QString("Phony-ID")) { MyMoneyAccount acc = file->account(s.accountId()); if (acc.isIncomeExpense()) { s.setPayeeId(QString()); t.modifySplit(s); } } } } } d->m_transaction = t; // make sure that the transaction does not have an id so that we can enter // it into the engine d->m_transaction.clearId(); } void MyMoneySchedule::setEndDate(const QDate& date) { Q_D(MyMoneySchedule); d->m_endDate = date; } void MyMoneySchedule::setLastDayInMonth(bool state) { Q_D(MyMoneySchedule); d->m_lastDayInMonth = state; } void MyMoneySchedule::setAutoEnter(bool autoenter) { Q_D(MyMoneySchedule); d->m_autoEnter = autoenter; } QDate MyMoneySchedule::startDate() const { Q_D(const MyMoneySchedule); if (d->m_startDate.isValid()) return d->m_startDate; return nextDueDate(); } eMyMoney::Schedule::PaymentType MyMoneySchedule::paymentType() const { Q_D(const MyMoneySchedule); return d->m_paymentType; } /** * Simple get method that returns true if the schedule is fixed. * * @return bool To indicate whether the instance is fixed. */ bool MyMoneySchedule::isFixed() const { Q_D(const MyMoneySchedule); return d->m_fixed; } /** * Simple get method that returns true if the schedule will end * at some time. * * @return bool Indicates whether the instance will end. */ bool MyMoneySchedule::willEnd() const { Q_D(const MyMoneySchedule); return d->m_endDate.isValid(); } QDate MyMoneySchedule::nextDueDate() const { Q_D(const MyMoneySchedule); return d->m_transaction.postDate(); } QDate MyMoneySchedule::adjustedNextDueDate() const { if (isFinished()) return QDate(); if (lastDayInMonth()) { QDate date = nextDueDate(); return adjustedDate(QDate(date.year(), date.month(), date.daysInMonth()), weekendOption()); } return adjustedDate(nextDueDate(), weekendOption()); } QDate MyMoneySchedule::adjustedDate(QDate date, Schedule::WeekendOption option) const { if (!date.isValid() || option == Schedule::WeekendOption::MoveNothing || isProcessingDate(date)) return date; int step = 1; if (option == Schedule::WeekendOption::MoveBefore) step = -1; while (!isProcessingDate(date)) date = date.addDays(step); return date; } void MyMoneySchedule::setNextDueDate(const QDate& date) { Q_D(MyMoneySchedule); if (date.isValid()) { d->m_transaction.setPostDate(date); // m_startDate = date; } } void MyMoneySchedule::setLastPayment(const QDate& date) { Q_D(MyMoneySchedule); // Delete all payments older than date QList::Iterator it; QList delList; for (it = d->m_recordedPayments.begin(); it != d->m_recordedPayments.end(); ++it) { if (*it < date || !date.isValid()) delList.append(*it); } for (it = delList.begin(); it != delList.end(); ++it) { d->m_recordedPayments.removeAll(*it); } d->m_lastPayment = date; if (!d->m_startDate.isValid()) d->m_startDate = date; } QString MyMoneySchedule::name() const { Q_D(const MyMoneySchedule); return d->m_name; } void MyMoneySchedule::setName(const QString& nm) { Q_D(MyMoneySchedule); d->m_name = nm; } eMyMoney::Schedule::WeekendOption MyMoneySchedule::weekendOption() const { Q_D(const MyMoneySchedule); return d->m_weekendOption; } void MyMoneySchedule::setOccurrence(Schedule::Occurrence occ) { auto occ2 = occ; auto mult = 1; simpleToCompoundOccurrence(mult, occ2); setOccurrencePeriod(occ2); setOccurrenceMultiplier(mult); } void MyMoneySchedule::setOccurrencePeriod(Schedule::Occurrence occ) { Q_D(MyMoneySchedule); d->m_occurrence = occ; } void MyMoneySchedule::setOccurrenceMultiplier(int occmultiplier) { Q_D(MyMoneySchedule); d->m_occurrenceMultiplier = occmultiplier < 1 ? 1 : occmultiplier; } void MyMoneySchedule::setType(Schedule::Type type) { Q_D(MyMoneySchedule); d->m_type = type; } void MyMoneySchedule::validate(bool id_check) const { /* Check the supplied instance is valid... * * To be valid it must not have the id set and have the following fields set: * * m_occurrence * m_type * m_startDate * m_paymentType * m_transaction * the transaction must contain at least one split (two is better ;-) ) */ - if (id_check && !m_id.isEmpty()) + Q_D(const MyMoneySchedule); + if (id_check && !d->m_id.isEmpty()) throw MYMONEYEXCEPTION("ID for schedule not empty when required"); - Q_D(const MyMoneySchedule); if (d->m_occurrence == Schedule::Occurrence::Any) throw MYMONEYEXCEPTION("Invalid occurrence type for schedule"); if (d->m_type == Schedule::Type::Any) throw MYMONEYEXCEPTION("Invalid type for schedule"); if (!nextDueDate().isValid()) throw MYMONEYEXCEPTION("Invalid next due date for schedule"); if (d->m_paymentType == Schedule::PaymentType::Any) throw MYMONEYEXCEPTION("Invalid payment type for schedule"); if (d->m_transaction.splitCount() == 0) throw MYMONEYEXCEPTION("Scheduled transaction does not contain splits"); // Check the payment types switch (d->m_type) { case Schedule::Type::Bill: if (d->m_paymentType == Schedule::PaymentType::DirectDeposit || d->m_paymentType == Schedule::PaymentType::ManualDeposit) throw MYMONEYEXCEPTION("Invalid payment type for bills"); break; case Schedule::Type::Deposit: if (d->m_paymentType == Schedule::PaymentType::DirectDebit || d->m_paymentType == Schedule::PaymentType::WriteChecque) throw MYMONEYEXCEPTION("Invalid payment type for deposits"); break; case Schedule::Type::Any: throw MYMONEYEXCEPTION("Invalid type ANY"); break; case Schedule::Type::Transfer: // if (m_paymentType == DirectDeposit || m_paymentType == ManualDeposit) // return false; break; case Schedule::Type::LoanPayment: break; } } QDate MyMoneySchedule::adjustedNextPayment(const QDate& refDate) const { return nextPaymentDate(true, refDate); } QDate MyMoneySchedule::adjustedNextPayment() const { return adjustedNextPayment(QDate::currentDate()); } QDate MyMoneySchedule::nextPayment(const QDate& refDate) const { return nextPaymentDate(false, refDate); } QDate MyMoneySchedule::nextPayment() const { return nextPayment(QDate::currentDate()); } QDate MyMoneySchedule::nextPaymentDate(const bool& adjust, const QDate& refDate) const { Schedule::WeekendOption option(adjust ? weekendOption() : Schedule::WeekendOption::MoveNothing); Q_D(const MyMoneySchedule); QDate adjEndDate(adjustedDate(d->m_endDate, option)); // if the enddate is valid and it is before the reference date, // then there will be no more payments. if (adjEndDate.isValid() && adjEndDate < refDate) { return QDate(); } QDate dueDate(nextDueDate()); QDate paymentDate(adjustedDate(dueDate, option)); if (paymentDate.isValid() && (paymentDate <= refDate || d->m_recordedPayments.contains(dueDate))) { switch (d->m_occurrence) { case Schedule::Occurrence::Once: // If the lastPayment is already set or the payment should have been // prior to the reference date then invalidate the payment date. if (d->m_lastPayment.isValid() || paymentDate <= refDate) paymentDate = QDate(); break; case Schedule::Occurrence::Daily: { int step = d->m_occurrenceMultiplier; do { dueDate = dueDate.addDays(step); paymentDate = adjustedDate(dueDate, option); } while (paymentDate.isValid() && (paymentDate <= refDate || d->m_recordedPayments.contains(dueDate))); } break; case Schedule::Occurrence::Weekly: { int step = 7 * d->m_occurrenceMultiplier; do { dueDate = dueDate.addDays(step); paymentDate = adjustedDate(dueDate, option); } while (paymentDate.isValid() && (paymentDate <= refDate || d->m_recordedPayments.contains(dueDate))); } break; case Schedule::Occurrence::EveryHalfMonth: do { dueDate = addHalfMonths(dueDate, d->m_occurrenceMultiplier); paymentDate = adjustedDate(dueDate, option); } while (paymentDate.isValid() && (paymentDate <= refDate || d->m_recordedPayments.contains(dueDate))); break; case Schedule::Occurrence::Monthly: do { dueDate = dueDate.addMonths(d->m_occurrenceMultiplier); fixDate(dueDate); paymentDate = adjustedDate(dueDate, option); } while (paymentDate.isValid() && (paymentDate <= refDate || d->m_recordedPayments.contains(dueDate))); break; case Schedule::Occurrence::Yearly: do { dueDate = dueDate.addYears(d->m_occurrenceMultiplier); fixDate(dueDate); paymentDate = adjustedDate(dueDate, option); } while (paymentDate.isValid() && (paymentDate <= refDate || d->m_recordedPayments.contains(dueDate))); break; case Schedule::Occurrence::Any: default: paymentDate = QDate(); break; } } if (paymentDate.isValid() && adjEndDate.isValid() && paymentDate > adjEndDate) paymentDate = QDate(); return paymentDate; } QDate MyMoneySchedule::nextPaymentDate(const bool& adjust) const { return nextPaymentDate(adjust, QDate::currentDate()); } QList MyMoneySchedule::paymentDates(const QDate& _startDate, const QDate& _endDate) const { QDate paymentDate(nextDueDate()); QList theDates; Schedule::WeekendOption option(weekendOption()); Q_D(const MyMoneySchedule); QDate endDate(_endDate); if (willEnd() && d->m_endDate < endDate) { // consider the adjusted end date instead of the plain end date endDate = adjustedDate(d->m_endDate, option); } QDate start_date(adjustedDate(startDate(), option)); // if the period specified by the parameters and the adjusted period // defined for this schedule don't overlap, then the list remains empty if ((willEnd() && adjustedDate(d->m_endDate, option) < _startDate) || start_date > endDate) return theDates; QDate date(adjustedDate(paymentDate, option)); switch (d->m_occurrence) { case Schedule::Occurrence::Once: if (start_date >= _startDate && start_date <= endDate) theDates.append(start_date); break; case Schedule::Occurrence::Daily: while (date.isValid() && (date <= endDate)) { if (date >= _startDate) theDates.append(date); paymentDate = paymentDate.addDays(d->m_occurrenceMultiplier); date = adjustedDate(paymentDate, option); } break; case Schedule::Occurrence::Weekly: { int step = 7 * d->m_occurrenceMultiplier; while (date.isValid() && (date <= endDate)) { if (date >= _startDate) theDates.append(date); paymentDate = paymentDate.addDays(step); date = adjustedDate(paymentDate, option); } } break; case Schedule::Occurrence::EveryHalfMonth: while (date.isValid() && (date <= endDate)) { if (date >= _startDate) theDates.append(date); paymentDate = addHalfMonths(paymentDate, d->m_occurrenceMultiplier); date = adjustedDate(paymentDate, option); } break; case Schedule::Occurrence::Monthly: while (date.isValid() && (date <= endDate)) { if (date >= _startDate) theDates.append(date); paymentDate = paymentDate.addMonths(d->m_occurrenceMultiplier); fixDate(paymentDate); date = adjustedDate(paymentDate, option); } break; case Schedule::Occurrence::Yearly: while (date.isValid() && (date <= endDate)) { if (date >= _startDate) theDates.append(date); paymentDate = paymentDate.addYears(d->m_occurrenceMultiplier); fixDate(paymentDate); date = adjustedDate(paymentDate, option); } break; case Schedule::Occurrence::Any: default: break; } return theDates; } bool MyMoneySchedule::operator <(const MyMoneySchedule& right) const { return adjustedNextDueDate() < right.adjustedNextDueDate(); } bool MyMoneySchedule::operator ==(const MyMoneySchedule& right) const { Q_D(const MyMoneySchedule); auto d2 = static_cast(right.d_func()); if (MyMoneyObject::operator==(right) && d->m_occurrence == d2->m_occurrence && d->m_occurrenceMultiplier == d2->m_occurrenceMultiplier && d->m_type == d2->m_type && d->m_startDate == d2->m_startDate && d->m_paymentType == d2->m_paymentType && d->m_fixed == d2->m_fixed && d->m_transaction == d2->m_transaction && d->m_endDate == d2->m_endDate && d->m_lastDayInMonth == d2->m_lastDayInMonth && d->m_autoEnter == d2->m_autoEnter && d->m_lastPayment == d2->m_lastPayment && ((d->m_name.length() == 0 && d2->m_name.length() == 0) || (d->m_name == d2->m_name))) return true; return false; } bool MyMoneySchedule::operator !=(const MyMoneySchedule& right) const { return ! operator==(right); } int MyMoneySchedule::transactionsRemaining() const { Q_D(const MyMoneySchedule); return transactionsRemainingUntil(adjustedDate(d->m_endDate, weekendOption())); } int MyMoneySchedule::transactionsRemainingUntil(const QDate& endDate) const { auto counter = 0; Q_D(const MyMoneySchedule); QDate startDate = d->m_lastPayment.isValid() ? d->m_lastPayment : d->m_startDate; if (startDate.isValid() && endDate.isValid()) { QList dates = paymentDates(startDate, endDate); counter = dates.count(); } return counter; } QDate MyMoneySchedule::endDate() const { Q_D(const MyMoneySchedule); return d->m_endDate; } bool MyMoneySchedule::autoEnter() const { Q_D(const MyMoneySchedule); return d->m_autoEnter; } bool MyMoneySchedule::lastDayInMonth() const { Q_D(const MyMoneySchedule); return d->m_lastDayInMonth; } MyMoneyTransaction MyMoneySchedule::transaction() const { Q_D(const MyMoneySchedule); return d->m_transaction; } QDate MyMoneySchedule::lastPayment() const { Q_D(const MyMoneySchedule); return d->m_lastPayment; } MyMoneyAccount MyMoneySchedule::account(int cnt) const { Q_D(const MyMoneySchedule); QList splits = d->m_transaction.splits(); QList::ConstIterator it; MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyAccount acc; // search the first asset or liability account for (it = splits.constBegin(); it != splits.constEnd() && (acc.id().isEmpty() || cnt); ++it) { try { acc = file->account((*it).accountId()); if (acc.isAssetLiability()) --cnt; if (!cnt) return acc; } catch (const MyMoneyException &) { qWarning("Schedule '%s' references unknown account '%s'", qPrintable(id()), qPrintable((*it).accountId())); return MyMoneyAccount(); } } return MyMoneyAccount(); } MyMoneyAccount MyMoneySchedule::transferAccount() const { return account(2); } QDate MyMoneySchedule::dateAfter(int transactions) const { auto counter = 1; QDate paymentDate(startDate()); if (transactions <= 0) return paymentDate; Q_D(const MyMoneySchedule); switch (d->m_occurrence) { case Schedule::Occurrence::Once: break; case Schedule::Occurrence::Daily: while (counter++ < transactions) paymentDate = paymentDate.addDays(d->m_occurrenceMultiplier); break; case Schedule::Occurrence::Weekly: { int step = 7 * d->m_occurrenceMultiplier; while (counter++ < transactions) paymentDate = paymentDate.addDays(step); } break; case Schedule::Occurrence::EveryHalfMonth: paymentDate = addHalfMonths(paymentDate, d->m_occurrenceMultiplier * (transactions - 1)); break; case Schedule::Occurrence::Monthly: while (counter++ < transactions) paymentDate = paymentDate.addMonths(d->m_occurrenceMultiplier); break; case Schedule::Occurrence::Yearly: while (counter++ < transactions) paymentDate = paymentDate.addYears(d->m_occurrenceMultiplier); break; case Schedule::Occurrence::Any: default: break; } return paymentDate; } bool MyMoneySchedule::isOverdue() const { if (isFinished()) return false; if (adjustedNextDueDate() >= QDate::currentDate()) return false; return true; } bool MyMoneySchedule::isFinished() const { Q_D(const MyMoneySchedule); if (!d->m_lastPayment.isValid()) return false; if (d->m_endDate.isValid()) { if (d->m_lastPayment >= d->m_endDate || !nextDueDate().isValid() || nextDueDate() > d->m_endDate) return true; } // Check to see if its a once off payment if (d->m_occurrence == Schedule::Occurrence::Once) return true; return false; } bool MyMoneySchedule::hasRecordedPayment(const QDate& date) const { Q_D(const MyMoneySchedule); // m_lastPayment should always be > recordedPayments() if (d->m_lastPayment.isValid() && d->m_lastPayment >= date) return true; if (d->m_recordedPayments.contains(date)) return true; return false; } void MyMoneySchedule::recordPayment(const QDate& date) { Q_D(MyMoneySchedule); d->m_recordedPayments.append(date); } QList MyMoneySchedule::recordedPayments() const { Q_D(const MyMoneySchedule); return d->m_recordedPayments; } void MyMoneySchedule::setWeekendOption(const Schedule::WeekendOption option) { Q_D(MyMoneySchedule); // make sure only valid values are used. Invalid defaults to MoveNothing. switch (option) { case Schedule::WeekendOption::MoveBefore: case Schedule::WeekendOption::MoveAfter: d->m_weekendOption = option; break; default: d->m_weekendOption = Schedule::WeekendOption::MoveNothing; break; } } void MyMoneySchedule::fixDate(QDate& date) const { Q_D(const MyMoneySchedule); QDate fixDate(d->m_startDate); if (fixDate.isValid() && date.day() != fixDate.day() && QDate::isValid(date.year(), date.month(), fixDate.day())) { date = QDate(date.year(), date.month(), fixDate.day()); } } void MyMoneySchedule::writeXML(QDomDocument& document, QDomElement& parent) const { auto el = document.createElement(nodeNames[nnScheduleTX]); - writeBaseXML(document, el); - Q_D(const MyMoneySchedule); + d->writeBaseXML(document, el); + el.setAttribute(d->getAttrName(Schedule::Attribute::Name), d->m_name); el.setAttribute(d->getAttrName(Schedule::Attribute::Type), (int)d->m_type); el.setAttribute(d->getAttrName(Schedule::Attribute::Occurrence), (int)d->m_occurrence); el.setAttribute(d->getAttrName(Schedule::Attribute::OccurrenceMultiplier), d->m_occurrenceMultiplier); el.setAttribute(d->getAttrName(Schedule::Attribute::PaymentType), (int)d->m_paymentType); el.setAttribute(d->getAttrName(Schedule::Attribute::StartDate), MyMoneyUtils::dateToString(d->m_startDate)); el.setAttribute(d->getAttrName(Schedule::Attribute::EndDate), MyMoneyUtils::dateToString(d->m_endDate)); el.setAttribute(d->getAttrName(Schedule::Attribute::Fixed), d->m_fixed); el.setAttribute(d->getAttrName(Schedule::Attribute::LastDayInMonth), d->m_lastDayInMonth); el.setAttribute(d->getAttrName(Schedule::Attribute::AutoEnter), d->m_autoEnter); el.setAttribute(d->getAttrName(Schedule::Attribute::LastPayment), MyMoneyUtils::dateToString(d->m_lastPayment)); el.setAttribute(d->getAttrName(Schedule::Attribute::WeekendOption), (int)d->m_weekendOption); //store the payment history for this scheduled task. QList payments = recordedPayments(); QList::ConstIterator it; QDomElement paymentsElement = document.createElement(d->getElName(Schedule::Element::Payments)); for (it = payments.constBegin(); it != payments.constEnd(); ++it) { QDomElement paymentEntry = document.createElement(d->getElName(Schedule::Element::Payment)); paymentEntry.setAttribute(d->getAttrName(Schedule::Attribute::Date), MyMoneyUtils::dateToString(*it)); paymentsElement.appendChild(paymentEntry); } el.appendChild(paymentsElement); //store the transaction data for this task. d->m_transaction.writeXML(document, el); parent.appendChild(el); } bool MyMoneySchedule::hasReferenceTo(const QString& id) const { Q_D(const MyMoneySchedule); return d->m_transaction.hasReferenceTo(id); } QString MyMoneySchedule::occurrenceToString() const { return occurrenceToString(occurrenceMultiplier(), occurrencePeriod()); } QString MyMoneySchedule::occurrenceToString(Schedule::Occurrence occurrence) { QString occurrenceString = I18N_NOOP2("Frequency of schedule", "Any"); if (occurrence == Schedule::Occurrence::Once) occurrenceString = I18N_NOOP2("Frequency of schedule", "Once"); else if (occurrence == Schedule::Occurrence::Daily) occurrenceString = I18N_NOOP2("Frequency of schedule", "Daily"); else if (occurrence == Schedule::Occurrence::Weekly) occurrenceString = I18N_NOOP2("Frequency of schedule", "Weekly"); else if (occurrence == Schedule::Occurrence::Fortnightly) occurrenceString = I18N_NOOP2("Frequency of schedule", "Fortnightly"); else if (occurrence == Schedule::Occurrence::EveryOtherWeek) occurrenceString = I18N_NOOP2("Frequency of schedule", "Every other week"); else if (occurrence == Schedule::Occurrence::EveryHalfMonth) occurrenceString = I18N_NOOP2("Frequency of schedule", "Every half month"); else if (occurrence == Schedule::Occurrence::EveryThreeWeeks) occurrenceString = I18N_NOOP2("Frequency of schedule", "Every three weeks"); else if (occurrence == Schedule::Occurrence::EveryFourWeeks) occurrenceString = I18N_NOOP2("Frequency of schedule", "Every four weeks"); else if (occurrence == Schedule::Occurrence::EveryThirtyDays) occurrenceString = I18N_NOOP2("Frequency of schedule", "Every thirty days"); else if (occurrence == Schedule::Occurrence::Monthly) occurrenceString = I18N_NOOP2("Frequency of schedule", "Monthly"); else if (occurrence == Schedule::Occurrence::EveryEightWeeks) occurrenceString = I18N_NOOP2("Frequency of schedule", "Every eight weeks"); else if (occurrence == Schedule::Occurrence::EveryOtherMonth) occurrenceString = I18N_NOOP2("Frequency of schedule", "Every two months"); else if (occurrence == Schedule::Occurrence::EveryThreeMonths) occurrenceString = I18N_NOOP2("Frequency of schedule", "Every three months"); else if (occurrence == Schedule::Occurrence::Quarterly) occurrenceString = I18N_NOOP2("Frequency of schedule", "Quarterly"); else if (occurrence == Schedule::Occurrence::EveryFourMonths) occurrenceString = I18N_NOOP2("Frequency of schedule", "Every four months"); else if (occurrence == Schedule::Occurrence::TwiceYearly) occurrenceString = I18N_NOOP2("Frequency of schedule", "Twice yearly"); else if (occurrence == Schedule::Occurrence::Yearly) occurrenceString = I18N_NOOP2("Frequency of schedule", "Yearly"); else if (occurrence == Schedule::Occurrence::EveryOtherYear) occurrenceString = I18N_NOOP2("Frequency of schedule", "Every other year"); return occurrenceString; } QString MyMoneySchedule::occurrenceToString(int mult, Schedule::Occurrence type) { QString occurrenceString = I18N_NOOP2("Frequency of schedule", "Any"); if (type == Schedule::Occurrence::Once) switch (mult) { case 1: occurrenceString = I18N_NOOP2("Frequency of schedule", "Once"); break; default: occurrenceString = I18N_NOOP2("Frequency of schedule", QString("%1 times").arg(mult)); } else if (type == Schedule::Occurrence::Daily) switch (mult) { case 1: occurrenceString = I18N_NOOP2("Frequency of schedule", "Daily"); break; case 30: occurrenceString = I18N_NOOP2("Frequency of schedule", "Every thirty days"); break; default: occurrenceString = I18N_NOOP2("Frequency of schedule", QString("Every %1 days").arg(mult)); } else if (type == Schedule::Occurrence::Weekly) switch (mult) { case 1: occurrenceString = I18N_NOOP2("Frequency of schedule", "Weekly"); break; case 2: occurrenceString = I18N_NOOP2("Frequency of schedule", "Every other week"); break; case 3: occurrenceString = I18N_NOOP2("Frequency of schedule", "Every three weeks"); break; case 4: occurrenceString = I18N_NOOP2("Frequency of schedule", "Every four weeks"); break; case 8: occurrenceString = I18N_NOOP2("Frequency of schedule", "Every eight weeks"); break; default: occurrenceString = I18N_NOOP2("Frequency of schedule", QString("Every %1 weeks").arg(mult)); } else if (type == Schedule::Occurrence::EveryHalfMonth) switch (mult) { case 1: occurrenceString = I18N_NOOP2("Frequency of schedule", "Every half month"); break; default: occurrenceString = I18N_NOOP2("Frequency of schedule", QString("Every %1 half months").arg(mult)); } else if (type == Schedule::Occurrence::Monthly) switch (mult) { case 1: occurrenceString = I18N_NOOP2("Frequency of schedule", "Monthly"); break; case 2: occurrenceString = I18N_NOOP2("Frequency of schedule", "Every two months"); break; case 3: occurrenceString = I18N_NOOP2("Frequency of schedule", "Every three months"); break; case 4: occurrenceString = I18N_NOOP2("Frequency of schedule", "Every four months"); break; case 6: occurrenceString = I18N_NOOP2("Frequency of schedule", "Twice yearly"); break; default: occurrenceString = I18N_NOOP2("Frequency of schedule", QString("Every %1 months").arg(mult)); } else if (type == Schedule::Occurrence::Yearly) switch (mult) { case 1: occurrenceString = I18N_NOOP2("Frequency of schedule", "Yearly"); break; case 2: occurrenceString = I18N_NOOP2("Frequency of schedule", "Every other year"); break; default: occurrenceString = I18N_NOOP2("Frequency of schedule", QString("Every %1 years").arg(mult)); } return occurrenceString; } QString MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence type) { QString occurrenceString = I18N_NOOP2("Schedule occurrence period", "Any"); if (type == Schedule::Occurrence::Once) occurrenceString = I18N_NOOP2("Schedule occurrence period", "Once"); else if (type == Schedule::Occurrence::Daily) occurrenceString = I18N_NOOP2("Schedule occurrence period", "Day"); else if (type == Schedule::Occurrence::Weekly) occurrenceString = I18N_NOOP2("Schedule occurrence period", "Week"); else if (type == Schedule::Occurrence::EveryHalfMonth) occurrenceString = I18N_NOOP2("Schedule occurrence period", "Half-month"); else if (type == Schedule::Occurrence::Monthly) occurrenceString = I18N_NOOP2("Schedule occurrence period", "Month"); else if (type == Schedule::Occurrence::Yearly) occurrenceString = I18N_NOOP2("Schedule occurrence period", "Year"); return occurrenceString; } QString MyMoneySchedule::scheduleTypeToString(Schedule::Type type) { QString text; switch (type) { case Schedule::Type::Bill: text = I18N_NOOP2("Scheduled transaction type", "Bill"); break; case Schedule::Type::Deposit: text = I18N_NOOP2("Scheduled transaction type", "Deposit"); break; case Schedule::Type::Transfer: text = I18N_NOOP2("Scheduled transaction type", "Transfer"); break; case Schedule::Type::LoanPayment: text = I18N_NOOP2("Scheduled transaction type", "Loan payment"); break; case Schedule::Type::Any: default: text = I18N_NOOP2("Scheduled transaction type", "Unknown"); } return text; } QString MyMoneySchedule::paymentMethodToString(Schedule::PaymentType paymentType) { QString text; switch (paymentType) { case Schedule::PaymentType::DirectDebit: text = I18N_NOOP2("Scheduled Transaction payment type", "Direct debit"); break; case Schedule::PaymentType::DirectDeposit: text = I18N_NOOP2("Scheduled Transaction payment type", "Direct deposit"); break; case Schedule::PaymentType::ManualDeposit: text = I18N_NOOP2("Scheduled Transaction payment type", "Manual deposit"); break; case Schedule::PaymentType::Other: text = I18N_NOOP2("Scheduled Transaction payment type", "Other"); break; case Schedule::PaymentType::WriteChecque: text = I18N_NOOP2("Scheduled Transaction payment type", "Write check"); break; case Schedule::PaymentType::StandingOrder: text = I18N_NOOP2("Scheduled Transaction payment type", "Standing order"); break; case Schedule::PaymentType::BankTransfer: text = I18N_NOOP2("Scheduled Transaction payment type", "Bank transfer"); break; case Schedule::PaymentType::Any: text = I18N_NOOP2("Scheduled Transaction payment type", "Any (Error)"); break; } return text; } QString MyMoneySchedule::weekendOptionToString(Schedule::WeekendOption weekendOption) { QString text; switch (weekendOption) { case Schedule::WeekendOption::MoveBefore: text = I18N_NOOP("Change the date to the previous processing day"); break; case Schedule::WeekendOption::MoveAfter: text = I18N_NOOP("Change the date to the next processing day"); break; case Schedule::WeekendOption::MoveNothing: text = I18N_NOOP("Do Nothing"); break; } return text; } // until we don't have the means to store the value // of the variation, we default to 10% in case this // scheduled transaction is marked 'not fixed'. // // ipwizard 2009-04-18 int MyMoneySchedule::variation() const { int rc = 0; if (!isFixed()) { rc = 10; #if 0 QString var = value("kmm-variation"); if (!var.isEmpty()) rc = var.toInt(); #endif } return rc; } void MyMoneySchedule::setVariation(int var) { Q_UNUSED(var) #if 0 deletePair("kmm-variation"); if (var != 0) setValue("kmm-variation", QString("%1").arg(var)); #endif } int MyMoneySchedule::eventsPerYear(Schedule::Occurrence occurrence) { int rc = 0; switch (occurrence) { case Schedule::Occurrence::Daily: rc = 365; break; case Schedule::Occurrence::Weekly: rc = 52; break; case Schedule::Occurrence::Fortnightly: rc = 26; break; case Schedule::Occurrence::EveryOtherWeek: rc = 26; break; case Schedule::Occurrence::EveryHalfMonth: rc = 24; break; case Schedule::Occurrence::EveryThreeWeeks: rc = 17; break; case Schedule::Occurrence::EveryFourWeeks: rc = 13; break; case Schedule::Occurrence::Monthly: case Schedule::Occurrence::EveryThirtyDays: rc = 12; break; case Schedule::Occurrence::EveryEightWeeks: rc = 6; break; case Schedule::Occurrence::EveryOtherMonth: rc = 6; break; case Schedule::Occurrence::EveryThreeMonths: case Schedule::Occurrence::Quarterly: rc = 4; break; case Schedule::Occurrence::EveryFourMonths: rc = 3; break; case Schedule::Occurrence::TwiceYearly: rc = 2; break; case Schedule::Occurrence::Yearly: rc = 1; break; default: qWarning("Occurrence not supported by financial calculator"); } return rc; } int MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence occurrence) { int rc = 0; switch (occurrence) { case Schedule::Occurrence::Daily: rc = 1; break; case Schedule::Occurrence::Weekly: rc = 7; break; case Schedule::Occurrence::Fortnightly: rc = 14; break; case Schedule::Occurrence::EveryOtherWeek: rc = 14; break; case Schedule::Occurrence::EveryHalfMonth: rc = 15; break; case Schedule::Occurrence::EveryThreeWeeks: rc = 21; break; case Schedule::Occurrence::EveryFourWeeks: rc = 28; break; case Schedule::Occurrence::EveryThirtyDays: rc = 30; break; case Schedule::Occurrence::Monthly: rc = 30; break; case Schedule::Occurrence::EveryEightWeeks: rc = 56; break; case Schedule::Occurrence::EveryOtherMonth: rc = 60; break; case Schedule::Occurrence::EveryThreeMonths: case Schedule::Occurrence::Quarterly: rc = 90; break; case Schedule::Occurrence::EveryFourMonths: rc = 120; break; case Schedule::Occurrence::TwiceYearly: rc = 180; break; case Schedule::Occurrence::Yearly: rc = 360; break; default: qWarning("Occurrence not supported by financial calculator"); } return rc; } QDate MyMoneySchedule::addHalfMonths(QDate date, int mult) const { QDate newdate = date; int d, dm; if (mult > 0) { d = newdate.day(); if (d <= 12) { if (mult % 2 == 0) newdate = newdate.addMonths(mult >> 1); else newdate = newdate.addMonths(mult >> 1).addDays(15); } else for (int i = 0; i < mult; i++) { if (d <= 13) newdate = newdate.addDays(15); else { dm = newdate.daysInMonth(); if (d == 14) newdate = newdate.addDays((dm < 30) ? dm - d : 15); else if (d == 15) newdate = newdate.addDays(dm - d); else if (d == dm) newdate = newdate.addDays(15 - d).addMonths(1); else newdate = newdate.addDays(-15).addMonths(1); } d = newdate.day(); } } else if (mult < 0) // Go backwards for (int i = 0; i > mult; i--) { d = newdate.day(); dm = newdate.daysInMonth(); if (d > 15) { dm = newdate.daysInMonth(); newdate = newdate.addDays((d == dm) ? 15 - dm : -15); } else if (d <= 13) newdate = newdate.addMonths(-1).addDays(15); else if (d == 15) newdate = newdate.addDays(-15); else { // 14 newdate = newdate.addMonths(-1); dm = newdate.daysInMonth(); newdate = newdate.addDays((dm < 30) ? dm - d : 15); } } return newdate; } /** * Helper method to convert simple occurrence to compound occurrence + multiplier * * @param multiplier Returned by reference. Adjusted multiplier * @param occurrence Returned by reference. Occurrence type */ void MyMoneySchedule::simpleToCompoundOccurrence(int& multiplier, Schedule::Occurrence& occurrence) { Schedule::Occurrence newOcc = occurrence; int newMulti = 1; if (occurrence == Schedule::Occurrence::Once || occurrence == Schedule::Occurrence::Daily || occurrence == Schedule::Occurrence::Weekly || occurrence == Schedule::Occurrence::EveryHalfMonth || occurrence == Schedule::Occurrence::Monthly || occurrence == Schedule::Occurrence::Yearly) { // Already a base occurrence and multiplier } else if (occurrence == Schedule::Occurrence::Fortnightly || occurrence == Schedule::Occurrence::EveryOtherWeek) { newOcc = Schedule::Occurrence::Weekly; newMulti = 2; } else if (occurrence == Schedule::Occurrence::EveryThreeWeeks) { newOcc = Schedule::Occurrence::Weekly; newMulti = 3; } else if (occurrence == Schedule::Occurrence::EveryFourWeeks) { newOcc = Schedule::Occurrence::Weekly; newMulti = 4; } else if (occurrence == Schedule::Occurrence::EveryThirtyDays) { newOcc = Schedule::Occurrence::Daily; newMulti = 30; } else if (occurrence == Schedule::Occurrence::EveryEightWeeks) { newOcc = Schedule::Occurrence::Weekly; newMulti = 8; } else if (occurrence == Schedule::Occurrence::EveryOtherMonth) { newOcc = Schedule::Occurrence::Monthly; newMulti = 2; } else if (occurrence == Schedule::Occurrence::EveryThreeMonths || occurrence == Schedule::Occurrence::Quarterly) { newOcc = Schedule::Occurrence::Monthly; newMulti = 3; } else if (occurrence == Schedule::Occurrence::EveryFourMonths) { newOcc = Schedule::Occurrence::Monthly; newMulti = 4; } else if (occurrence == Schedule::Occurrence::TwiceYearly) { newOcc = Schedule::Occurrence::Monthly; newMulti = 6; } else if (occurrence == Schedule::Occurrence::EveryOtherYear) { newOcc = Schedule::Occurrence::Yearly; newMulti = 2; } else { // Unknown newOcc = Schedule::Occurrence::Any; newMulti = 1; } if (newOcc != occurrence) { occurrence = newOcc; multiplier = newMulti == 1 ? multiplier : newMulti * multiplier; } } /** * Helper method to convert compound occurrence + multiplier to simple occurrence * * @param multiplier Returned by reference. Adjusted multiplier * @param occurrence Returned by reference. Occurrence type */ void MyMoneySchedule::compoundToSimpleOccurrence(int& multiplier, Schedule::Occurrence& occurrence) { Schedule::Occurrence newOcc = occurrence; if (occurrence == Schedule::Occurrence::Once) { // Nothing to do } else if (occurrence == Schedule::Occurrence::Daily) { switch (multiplier) { case 1: break; case 30: newOcc = Schedule::Occurrence::EveryThirtyDays; break; } } else if (newOcc == Schedule::Occurrence::Weekly) { switch (multiplier) { case 1: break; case 2: newOcc = Schedule::Occurrence::EveryOtherWeek; break; case 3: newOcc = Schedule::Occurrence::EveryThreeWeeks; break; case 4: newOcc = Schedule::Occurrence::EveryFourWeeks; break; case 8: newOcc = Schedule::Occurrence::EveryEightWeeks; break; } } else if (occurrence == Schedule::Occurrence::Monthly) switch (multiplier) { case 1: break; case 2: newOcc = Schedule::Occurrence::EveryOtherMonth; break; case 3: newOcc = Schedule::Occurrence::EveryThreeMonths; break; case 4: newOcc = Schedule::Occurrence::EveryFourMonths; break; case 6: newOcc = Schedule::Occurrence::TwiceYearly; break; } else if (occurrence == Schedule::Occurrence::EveryHalfMonth) switch (multiplier) { case 1: break; } else if (occurrence == Schedule::Occurrence::Yearly) { switch (multiplier) { case 1: break; case 2: newOcc = Schedule::Occurrence::EveryOtherYear; break; } } if (occurrence != newOcc) { // Changed to derived type occurrence = newOcc; multiplier = 1; } } void MyMoneySchedule::setProcessingCalendar(IMyMoneyProcessingCalendar* pc) { processingCalendarPtr = pc; } bool MyMoneySchedule::isProcessingDate(const QDate& date) const { if (processingCalendarPtr) return processingCalendarPtr->isProcessingDate(date); return date.dayOfWeek() < Qt::Saturday; } IMyMoneyProcessingCalendar* MyMoneySchedule::processingCalendar() const { return processingCalendarPtr; } bool MyMoneySchedule::replaceId(const QString& newId, const QString& oldId) { Q_D(MyMoneySchedule); return d->m_transaction.replaceId(newId, oldId); } diff --git a/kmymoney/mymoney/mymoneyschedule.h b/kmymoney/mymoney/mymoneyschedule.h index a6d9a108b..e2bf85849 100644 --- a/kmymoney/mymoney/mymoneyschedule.h +++ b/kmymoney/mymoney/mymoneyschedule.h @@ -1,734 +1,732 @@ /*************************************************************************** mymoneyschedule.h ------------------- copyright : (C) 2000-2002 by Michael Edwardes (C) 2007 by Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MYMONEYSCHEDULE_H #define MYMONEYSCHEDULE_H // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "kmm_mymoney_export.h" #include "mymoneyunittestable.h" #include "mymoneyobject.h" class QString; class QDate; class IMyMoneyProcessingCalendar; class MyMoneyAccount; class MyMoneyTransaction; namespace eMyMoney { namespace Schedule { enum class Type; enum class Occurrence; enum class PaymentType; enum class WeekendOption; } } template class QList; /** * @author Michael Edwardes */ /** * This class represents a schedule. (A series of bills, deposits or * transfers). * * @short A class to represent a schedule. * @see MyMoneyScheduled */ class MyMoneySchedulePrivate; class KMM_MYMONEY_EXPORT MyMoneySchedule : public MyMoneyObject { Q_DECLARE_PRIVATE(MyMoneySchedule) - MyMoneySchedulePrivate* d_ptr; friend class MyMoneyStorageANON; KMM_MYMONEY_UNIT_TESTABLE public: /** * Standard constructor */ MyMoneySchedule(); /** * Constructor for initialising the object. * * Please note that the optional fields are not set and the transaction * MUST be set before it can be used. * * @a startDate is not used anymore and internally set to QDate() */ explicit MyMoneySchedule(const QString& name, eMyMoney::Schedule::Type type, eMyMoney::Schedule::Occurrence occurrence, int occurrenceMultiplier, eMyMoney::Schedule::PaymentType paymentType, const QDate& startDate, const QDate& endDate, bool fixed, bool autoEnter); explicit MyMoneySchedule(const QDomElement& node); MyMoneySchedule(const QString& id, const MyMoneySchedule& other); MyMoneySchedule(const MyMoneySchedule & other); MyMoneySchedule(MyMoneySchedule && other); MyMoneySchedule & operator=(MyMoneySchedule other); friend void swap(MyMoneySchedule& first, MyMoneySchedule& second); /** * Standard destructor */ ~MyMoneySchedule(); /** * Simple get method that returns the occurrence frequency. * * @return eMyMoney::Schedule::Occurrence The instance frequency. */ eMyMoney::Schedule::Occurrence occurrence() const; /** * Simple get method that returns the occurrence period * multiplier and occurrence * * @return eMyMoney::Schedule::Occurrence The instance period * */ eMyMoney::Schedule::Occurrence occurrencePeriod() const; /** * Simple get method that returns the occurrence period multiplier. * * @return int The frequency multiplier */ int occurrenceMultiplier() const; /** * Simple get method that returns the schedule type. * * @return eMyMoney::Schedule::Type The instance type. */ eMyMoney::Schedule::Type type() const; /** * Simple get method that returns the schedule startDate. If * the schedule has been executed once, the date of the first * execution is returned. Otherwise, the next due date is * returned. * * @return reference to QDate containing the start date. */ QDate startDate() const; /** * Simple get method that returns the schedule paymentType. * * @return eMyMoney::Schedule::PaymentType The instance paymentType. */ eMyMoney::Schedule::PaymentType paymentType() const; /** * Simple get method that returns true if the schedule is fixed. * * @return bool To indicate whether the instance is fixed. */ bool isFixed() const; /** * Simple get method that returns true if the schedule will end * at some time. * * @return bool Indicates whether the instance will end. */ bool willEnd() const; /** * Simple get method that returns the number of transactions remaining. * * @return int The number of transactions remaining for the instance. */ int transactionsRemaining() const; /** * Simple method that returns the number of transactions remaining * until a given date. * * @param endDate Date to count transactions to. * @return int The number of transactions remaining for the instance. */ int transactionsRemainingUntil(const QDate& endDate) const; /** * Simple get method that returns the schedule end date. * * @return QDate The end date for the instance. */ QDate endDate() const; /** * Get the state if the schedule should be processed at the last day * of a month * * @return state of the flag */ bool lastDayInMonth() const; /** * Simple get method that returns true if the transaction should be * automatically entered into the register. * * @return bool Indicates whether the instance will be automatically entered. */ bool autoEnter() const; /** * Simple get method that returns the transaction data for the schedule. * * @return MyMoneyTransaction The transaction data for the instance. */ MyMoneyTransaction transaction() const; /** * Simple method that returns the schedules last payment. If the * schedule has never been executed, QDate() will be returned. * * @return QDate The last payment for the schedule. */ QDate lastPayment() const; /** * Simple method that returns the next due date for the schedule. * * @return reference to QDate containing the next due date. * * @note The date returned can represent a value that is past * a possible end of the schedule. Make sure to consider * the return value of isFinished() when using the value returned. */ QDate nextDueDate() const; /** * This method returns the next due date adjusted * according to the rules specified by the schedule's weekend option. * * @return QDate containing the adjusted next due date. If the * schedule is finished (@sa isFinished()) then the method * returns an invalid QDate. * * @sa weekendOption() * @sa adjustedDate() */ QDate adjustedNextDueDate() const; /** * This method adjusts returns the date adjusted according to the * rules specified by the schedule's weekend option. * * @return QDate containing the adjusted date. */ QDate adjustedDate(QDate date, eMyMoney::Schedule::WeekendOption option) const; /** * Get the weekendOption that determines how the schedule check code * will enter transactions that occur on a non-processing day (usually * a weekend). * * This not used by MyMoneySchedule but by the support code. **/ eMyMoney::Schedule::WeekendOption weekendOption() const; /** * Simple method that sets the frequency for the schedule. * * @param occ The new occurrence (frequency). * @return none */ void setOccurrence(eMyMoney::Schedule::Occurrence occ); /** * Simple method that sets the schedule period * * @param occ The new occurrence period (frequency) * @return none */ void setOccurrencePeriod(eMyMoney::Schedule::Occurrence occ); /** * Simple method that sets the frequency multiplier for the schedule. * * @param occmultiplier The new occurrence (frequency) multiplier. * @return none */ void setOccurrenceMultiplier(int occmultiplier); /** * Simple method that sets the type for the schedule. * * @param type The new type. * @return none */ void setType(eMyMoney::Schedule::Type type); /** * Simple method that sets the start date for the schedule. * * @param date The new start date. * @return none */ void setStartDate(const QDate& date); /** * Simple method that sets the payment type for the schedule. * * @param type The new payment type. * @return none */ void setPaymentType(eMyMoney::Schedule::PaymentType type); /** * Simple method to set whether the schedule is fixed or not. * * @param fixed boolean to indicate whether the instance is fixed. * @return none */ void setFixed(bool fixed); /** * Simple method that sets the transaction for the schedule. * The transaction must have a valid postDate set, otherwise * it will not be accepted. * * @param transaction The new transaction. * @return none */ void setTransaction(const MyMoneyTransaction& transaction); /** * Simple set method to set the end date for the schedule. * * @param date The new end date. * @return none */ void setEndDate(const QDate& date); /** * Simple method to set whether the schedule should be performed at * the last day of a month. * * @param state boolean The state to set * @return none */ void setLastDayInMonth(bool state); /** * Simple set method to set whether this transaction should be automatically * entered into the journal whenever it is due. * * @param autoenter boolean to indicate whether we need to automatically * enter the transaction. * @return none */ void setAutoEnter(bool autoenter); /** * Simple set method to set the schedule's next payment date. * * @param date The next payment date. * @return none */ void setNextDueDate(const QDate& date); /** * Simple set method to set the schedule's last payment. If * this method is called for the first time on the object, * the @a m_startDate member will be set to @a date as well. * * This method should be called whenever a schedule is entered or skipped. * * @param date The last payment date. * @return none */ void setLastPayment(const QDate& date); /** * Set the weekendOption that determines how the schedule check code * will enter transactions that occur on a non-processing day (usually * a weekend). The following values * are valid: * * - MoveNothing: don't modify date * - MoveBefore: modify the date to the previous processing day * - MoveAfter: modify the date to the next processing day * * If an invalid option is given, the option is set to MoveNothing. * * @param option See list in description * @return none * * @note This not used by MyMoneySchedule but by the support code. **/ void setWeekendOption(const eMyMoney::Schedule::WeekendOption option); /** * Validates the schedule instance. * * Makes sure the paymentType matches the type and that the required * fields have been set. * * @param id_check if @p true, the method will check for an empty id. * if @p false, this check is skipped. Default is @p true. * * @return If this method returns, all checks are passed. Otherwise, * it will throw a MyMoneyException object. * * @exception MyMoneyException with detailed error information is thrown * in case of failure of any check. */ void validate(bool id_check = true) const; /** * Calculates the date of the next payment adjusted according to the * rules specified by the schedule's weekend option. * * @param refDate The reference date from which the next payment * date will be calculated (defaults to current date) * * @return QDate The adjusted date the next payment is due. This date is * always past @a refDate. In case of an error or if there * are no more payments then an empty/invalid QDate() will * be returned. */ QDate adjustedNextPayment(const QDate& refDate) const; QDate adjustedNextPayment() const; /** * Calculates the date of the next payment. * * @param refDate The reference date from which the next payment * date will be calculated (defaults to current date) * * @return QDate The date the next payment is due. This date is * always past @a refDate. In case of an error or * if there are no more payments then an empty/invalid QDate() * will be returned. */ QDate nextPayment(const QDate& refDate) const; QDate nextPayment() const; /** * Calculates the date of the next payment and adjusts if asked. * * @param adjust Whether to adjust the calculated date according to the * rules specified by the schedule's weekend option. * @param refDate The reference date from which the next payment * date will be calculated (defaults to current date) * * @return QDate The date the next payment is due. This date is * always past @a refDate. In case of an error or * if there is no more payments then an empty/invalid QDate() * will be returned. */ QDate nextPaymentDate(const bool& adjust, const QDate& refDate) const; QDate nextPaymentDate(const bool& adjust) const; /** * Calculates the dates of the payment over a certain period of time. * * An empty list is returned for no payments or error. * * @param startDate The start date for the range calculations * @param endDate The end date for the range calculations. * @return QList The dates on which the payments are due. */ QList paymentDates(const QDate& startDate, const QDate& endDate) const; /** * Returns the instances name * * @return The name */ QString name() const; /** * Changes the instance name * * @param nm The new name * @return none */ void setName(const QString& nm); bool operator ==(const MyMoneySchedule& right) const; bool operator !=(const MyMoneySchedule& right) const; bool operator <(const MyMoneySchedule& right) const; MyMoneyAccount account(int cnt = 1) const; MyMoneyAccount transferAccount() const; QDate dateAfter(int transactions) const; bool isOverdue() const; bool isFinished() const; bool hasRecordedPayment(const QDate&) const; void recordPayment(const QDate&); QList recordedPayments() const; void writeXML(QDomDocument& document, QDomElement& parent) const; /** * This method checks if a reference to the given object exists. It returns, * a @p true if the object is referencing the one requested by the * parameter @p id. If it does not, this method returns @p false. * * @param id id of the object to be checked for references * @retval true This object references object with id @p id. * @retval false This object does not reference the object with id @p id. */ virtual bool hasReferenceTo(const QString& id) const; /** * This method replaces all occurrences of id @a oldId with * @a newId. All other ids are not changed. * * @return true if any change has been performed * @return false if nothing has been modified */ bool replaceId(const QString& newId, const QString& oldId); /** * Returns the human-readable format of Schedule's occurrence * * @return QString representing the human readable format */ QString occurrenceToString() const; /** * This method is used to convert the occurrence type from its * internal representation into a human readable format. * * @param type numerical representation of the MyMoneySchedule * occurrence type * * @return QString representing the human readable format */ static QString occurrenceToString(eMyMoney::Schedule::Occurrence type); /** * This method is used to convert a multiplier and base occurrence type * from its internal representation into a human readable format. * When multiplier * occurrence is equivalent to a simple occurrence * the method returns the same as occurrenceToString of the simple occurrence * * @param mult occurrence multiplier * @param type occurrence period * * @return QString representing the human readable format */ static QString occurrenceToString(int mult, eMyMoney::Schedule::Occurrence type); /** * This method is used to convert an occurrence period from * its internal representation into a human-readable format. * * @param type numerical representation of the MyMoneySchedule * occurrence type * * @return QString representing the human readable format */ static QString occurrencePeriodToString(eMyMoney::Schedule::Occurrence type); /** * This method is used to convert the payment type from its * internal representation into a human readable format. * * @param paymentType numerical representation of the MyMoneySchedule * payment type * * @return QString representing the human readable format */ static QString paymentMethodToString(eMyMoney::Schedule::PaymentType paymentType); /** * This method is used to convert the schedule weekend option from its * internal representation into a human readable format. * * @param weekendOption numerical representation of the MyMoneySchedule * weekend option * * @return QString representing the human readable format */ static QString weekendOptionToString(eMyMoney::Schedule::WeekendOption weekendOption); /** * This method is used to convert the schedule type from its * internal representation into a human readable format. * * @param type numerical representation of the MyMoneySchedule * schedule type * * @return QString representing the human readable format */ static QString scheduleTypeToString(eMyMoney::Schedule::Type type); int variation() const; void setVariation(int var); /** * * Convert an occurrence to the maximum number of events possible during a single * calendar year. * A fortnight is treated as 15 days. * * @param occurrence The occurrence * * @return int Number of days between events */ static int eventsPerYear(eMyMoney::Schedule::Occurrence occurrence); /** * * Convert an occurrence to the number of days between events * Treats a month as 30 days. * Treats a fortnight as 15 days. * * @param occurrence The occurrence * * @return int Number of days between events */ static int daysBetweenEvents(eMyMoney::Schedule::Occurrence occurrence); /** * Helper method to convert simple occurrence to compound occurrence + multiplier * * @param multiplier Returned by reference. Adjusted multiplier * @param occurrence Returned by reference. Occurrence type */ static void simpleToCompoundOccurrence(int& multiplier, eMyMoney::Schedule::Occurrence& occurrence); /** * Helper method to convert compound occurrence + multiplier to simple occurrence * * @param multiplier Returned by reference. Adjusted multiplier * @param occurrence Returned by reference. Occurrence type */ static void compoundToSimpleOccurrence(int& multiplier, eMyMoney::Schedule::Occurrence& occurrence); /** * This method is used to set the static point to relevant * IMyMoneyProcessingCalendar. */ static void setProcessingCalendar(IMyMoneyProcessingCalendar* pc); private: /** * This method returns a pointer to the processing calendar object. * * @return const pointer to the current attached processing calendar object. * If no object is attached, returns 0. */ IMyMoneyProcessingCalendar* processingCalendar() const; /** * This method forces the day of the passed @p date to * be the day of the start date of this schedule kept * in m_startDate. It is internally used when calculating * the payment dates over several periods. * * @param date reference to QDate object to be checked and adjusted */ void fixDate(QDate& date) const; /** * Simple method that sets the transaction for the schedule. * The transaction must have a valid postDate set, otherwise * it will not be accepted. This test is bypassed, if @a noDateCheck * is set to true * * @param transaction The new transaction. * @param noDateCheck if @a true, the date check is bypassed * @return none */ void setTransaction(const MyMoneyTransaction& transaction, bool noDateCheck); /** * This method adds a number of Half Months to the given Date. * This is used for EveryHalfMonth occurrences. * The addition uses the following rules to add a half month: * Day 1-13: add 15 days * Day 14: add 15 days (except February: the last day of the month) * Day 15: last day of the month * Day 16-29 (not last day in February): subtract 15 days and add 1 month * 30 and last day: 15th of next month * * This calculation pairs days 1 to 12 with 16 to 27. * Day 15 is paired with the last day of every month. * Repeated addition has issues in the following cases: * - Days 13 to 14 are paired with 28 to 29 until addition hits the last day of February * after which the (15,last) pair will be used. * - Addition from Day 30 leads immediately to the (15th,last) day pair. * * @param date The date * @param mult The number of half months to add. Default is 1. * * @return QDate date with mult half months added */ QDate addHalfMonths(QDate date, int mult = 1) const; /** * Checks if a given date should be considered a processing day * based on a calendar. See @a IMyMoneyProcessingCalendar and * setProcessingCalendar(). If no processingCalendar has been * setup using setProcessingCalendar it returns @c true on Mon..Fri * and @c false on Sat..Sun. */ bool isProcessingDate(const QDate& date) const; }; inline void swap(MyMoneySchedule& first, MyMoneySchedule& second) // krazy:exclude=inline { using std::swap; swap(first.d_ptr, second.d_ptr); - swap(first.m_id, second.m_id); } inline MyMoneySchedule::MyMoneySchedule(MyMoneySchedule && other) : MyMoneySchedule() // krazy:exclude=inline { swap(*this, other); } inline MyMoneySchedule & MyMoneySchedule::operator=(MyMoneySchedule other) // krazy:exclude=inline { swap(*this, other); return *this; } /** * Make it possible to hold @ref MyMoneySchedule objects inside @ref QVariant objects. */ Q_DECLARE_METATYPE(MyMoneySchedule) #endif diff --git a/kmymoney/mymoney/mymoneyschedule_p.h b/kmymoney/mymoney/mymoneyschedule_p.h index 807626387..60d35e117 100644 --- a/kmymoney/mymoney/mymoneyschedule_p.h +++ b/kmymoney/mymoney/mymoneyschedule_p.h @@ -1,153 +1,154 @@ /*************************************************************************** mymoneyschedule.cpp ------------------- copyright : (C) 2000-2002 by Michael Edwardes (C) 2007 by Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MYMONEYSCHEDULE_P_H #define MYMONEYSCHEDULE_P_H // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes +#include "mymoneyobject_p.h" #include "mymoneytransaction.h" #include "mymoneyenums.h" using namespace eMyMoney; namespace eMyMoney { namespace Schedule { enum class Element { Payment, Payments }; enum class Attribute { Name = 0, Type, Occurrence, OccurrenceMultiplier, PaymentType, Fixed, AutoEnter, LastPayment, WeekendOption, Date, StartDate, EndDate, LastDayInMonth, // insert new entries above this line LastAttribute }; uint qHash(const Attribute key, uint seed) { return ::qHash(static_cast(key), seed); } } } -class MyMoneySchedulePrivate { - +class MyMoneySchedulePrivate : public MyMoneyObjectPrivate +{ public: MyMoneySchedulePrivate() : m_occurrence(Schedule::Occurrence::Any) , m_occurrenceMultiplier(1) , m_type(Schedule::Type::Any) , m_paymentType(Schedule::PaymentType::Any) , m_fixed(false) , m_lastDayInMonth(false) , m_autoEnter(false) , m_weekendOption(Schedule::WeekendOption::MoveNothing) {} static QString getElName(const Schedule::Element el) { - static const QMap elNames = { + static const QMap elNames { {Schedule::Element::Payment, QStringLiteral("PAYMENT")}, {Schedule::Element::Payments, QStringLiteral("PAYMENTS")} }; return elNames[el]; } static QString getAttrName(const Schedule::Attribute attr) { - static const QHash attrNames = { + static const QHash attrNames { {Schedule::Attribute::Name, QStringLiteral("name")}, {Schedule::Attribute::Type, QStringLiteral("type")}, {Schedule::Attribute::Occurrence, QStringLiteral("occurence")}, // krazy:exclude=spelling {Schedule::Attribute::OccurrenceMultiplier, QStringLiteral("occurenceMultiplier")}, // krazy:exclude=spelling {Schedule::Attribute::PaymentType, QStringLiteral("paymentType")}, {Schedule::Attribute::Fixed, QStringLiteral("fixed")}, {Schedule::Attribute::AutoEnter, QStringLiteral("autoEnter")}, {Schedule::Attribute::LastPayment, QStringLiteral("lastPayment")}, {Schedule::Attribute::WeekendOption, QStringLiteral("weekendOption")}, {Schedule::Attribute::Date, QStringLiteral("date")}, {Schedule::Attribute::StartDate, QStringLiteral("startDate")}, {Schedule::Attribute::EndDate, QStringLiteral("endDate")}, {Schedule::Attribute::LastDayInMonth, QStringLiteral("lastDayInMonth")} }; return attrNames[attr]; } /// Its occurrence eMyMoney::Schedule::Occurrence m_occurrence; /// Its occurrence multiplier int m_occurrenceMultiplier; /// Its type eMyMoney::Schedule::Type m_type; /// The date the schedule commences QDate m_startDate; /// The payment type eMyMoney::Schedule::PaymentType m_paymentType; /// Can the amount vary bool m_fixed; /// The, possibly estimated, amount plus all other relevant details MyMoneyTransaction m_transaction; /// The last transaction date if the schedule does end at a fixed date QDate m_endDate; /// the last day in month flag bool m_lastDayInMonth; /// Enter the transaction into the register automatically bool m_autoEnter; /// Internal date used for calculations QDate m_lastPayment; /// The name QString m_name; /// The recorded payments QList m_recordedPayments; /// The weekend option eMyMoney::Schedule::WeekendOption m_weekendOption; }; #endif diff --git a/kmymoney/mymoney/mymoneysecurity.cpp b/kmymoney/mymoney/mymoneysecurity.cpp index 38ff1bfe0..5d78fc38f 100644 --- a/kmymoney/mymoney/mymoneysecurity.cpp +++ b/kmymoney/mymoney/mymoneysecurity.cpp @@ -1,358 +1,352 @@ /*************************************************************************** mymoneysecurity.cpp - description ------------------- begin : Tue Jan 29 2002 copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio (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. * * * ***************************************************************************/ #include "mymoneysecurity.h" #include "mymoneysecurity_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyexception.h" #include "mymoneystoragenames.h" using namespace eMyMoney; using namespace MyMoneyStorageNodes; MyMoneySecurity::MyMoneySecurity() : - d_ptr(new MyMoneySecurityPrivate) + MyMoneyObject(*new MyMoneySecurityPrivate) { } MyMoneySecurity::MyMoneySecurity(const QString& id, const QString& name, const QString& symbol, const int smallestCashFraction, const int smallestAccountFraction, const int pricePrecision) : - MyMoneyObject(id), - MyMoneyKeyValueContainer(), - d_ptr(new MyMoneySecurityPrivate) + MyMoneyObject(*new MyMoneySecurityPrivate, id), + MyMoneyKeyValueContainer() { Q_D(MyMoneySecurity); d->m_name = name; d->m_smallestCashFraction = smallestCashFraction; d->m_pricePrecision = pricePrecision; d->m_securityType = eMyMoney::Security::Type::Currency; if (symbol.isEmpty()) d->m_tradingSymbol = id; else d->m_tradingSymbol = symbol; if (smallestAccountFraction) d->m_smallestAccountFraction = smallestAccountFraction; else d->m_smallestAccountFraction = smallestCashFraction; } MyMoneySecurity::MyMoneySecurity(const QDomElement& node) : - MyMoneyObject(node), - MyMoneyKeyValueContainer(node.elementsByTagName(nodeNames[nnKeyValuePairs]).item(0).toElement()), - d_ptr(new MyMoneySecurityPrivate) + MyMoneyObject(*new MyMoneySecurityPrivate, node), + MyMoneyKeyValueContainer(node.elementsByTagName(nodeNames[nnKeyValuePairs]).item(0).toElement()) { { const auto tag = node.tagName(); if ((nodeNames[nnSecurity] != tag) && (nodeNames[nnEquity] != tag) && (nodeNames[nnCurrency] != tag)) throw MYMONEYEXCEPTION("Node was not SECURITY or CURRENCY"); } Q_D(MyMoneySecurity); d->m_name = node.attribute(d->getAttrName(Security::Attribute::Name)); d->m_tradingSymbol = node.attribute(d->getAttrName(Security::Attribute::Symbol)); d->m_securityType = static_cast(node.attribute(d->getAttrName(Security::Attribute::Type)).toInt()); d->m_roundingMethod = static_cast(node.attribute(d->getAttrName(Security::Attribute::RoundingMethod)).toInt()); d->m_smallestAccountFraction = node.attribute(d->getAttrName(Security::Attribute::SAF)).toUInt(); d->m_pricePrecision = node.attribute(d->getAttrName(Security::Attribute::PP)).toUInt(); if (d->m_smallestAccountFraction == 0) d->m_smallestAccountFraction = 100; if (d->m_pricePrecision == 0 || d->m_pricePrecision > 10) d->m_pricePrecision = 4; if (isCurrency()) { d->m_smallestCashFraction = node.attribute(d->getAttrName(Security::Attribute::SCF)).toUInt(); if (d->m_smallestCashFraction == 0) d->m_smallestCashFraction = 100; } else { d->m_tradingCurrency = node.attribute(d->getAttrName(Security::Attribute::TradingCurrency)); d->m_tradingMarket = node.attribute(d->getAttrName(Security::Attribute::TradingMarket)); } } MyMoneySecurity::MyMoneySecurity(const MyMoneySecurity& other) : - MyMoneyObject(other.id()), - MyMoneyKeyValueContainer(other), - d_ptr(new MyMoneySecurityPrivate(*other.d_func())) + MyMoneyObject(*new MyMoneySecurityPrivate(*other.d_func()), other.id()), + MyMoneyKeyValueContainer(other) { } MyMoneySecurity::MyMoneySecurity(const QString& id, const MyMoneySecurity& other) : - MyMoneyObject(id), - MyMoneyKeyValueContainer(other), - d_ptr(new MyMoneySecurityPrivate(*other.d_func())) + MyMoneyObject(*new MyMoneySecurityPrivate(*other.d_func()), id), + MyMoneyKeyValueContainer(other) { } MyMoneySecurity::~MyMoneySecurity() { - Q_D(MyMoneySecurity); - delete d; } bool MyMoneySecurity::operator == (const MyMoneySecurity& right) const { Q_D(const MyMoneySecurity); auto d2 = static_cast(right.d_func()); - return (m_id == right.m_id) + return (d->m_id == d2->m_id) && (d->m_name == d2->m_name) && (d->m_tradingSymbol == d2->m_tradingSymbol) && (d->m_tradingMarket == d2->m_tradingMarket) && (d->m_roundingMethod == d2->m_roundingMethod) && (d->m_tradingSymbol == d2->m_tradingSymbol) && (d->m_tradingCurrency == d2->m_tradingCurrency) && (d->m_securityType == d2->m_securityType) && (d->m_smallestAccountFraction == d2->m_smallestAccountFraction) && (d->m_smallestCashFraction == d2->m_smallestCashFraction) && (d->m_pricePrecision == d2->m_pricePrecision) && this->MyMoneyKeyValueContainer::operator == (right); } bool MyMoneySecurity::operator < (const MyMoneySecurity& right) const { Q_D(const MyMoneySecurity); auto d2 = static_cast(right.d_func()); if (d->m_securityType == d2->m_securityType) return d->m_name < d2->m_name; return d->m_securityType < d2->m_securityType; } QString MyMoneySecurity::name() const { Q_D(const MyMoneySecurity); return d->m_name; } void MyMoneySecurity::setName(const QString& str) { Q_D(MyMoneySecurity); d->m_name = str; } QString MyMoneySecurity::tradingSymbol() const { Q_D(const MyMoneySecurity); return d->m_tradingSymbol; } void MyMoneySecurity::setTradingSymbol(const QString& str) { Q_D(MyMoneySecurity); d->m_tradingSymbol = str; } QString MyMoneySecurity::tradingMarket() const { Q_D(const MyMoneySecurity); return d->m_tradingMarket; } void MyMoneySecurity::setTradingMarket(const QString& str) { Q_D(MyMoneySecurity); d->m_tradingMarket = str; } QString MyMoneySecurity::tradingCurrency() const { Q_D(const MyMoneySecurity); return d->m_tradingCurrency; } void MyMoneySecurity::setTradingCurrency(const QString& str) { Q_D(MyMoneySecurity); d->m_tradingCurrency = str; } bool MyMoneySecurity::operator != (const MyMoneySecurity& r) const { return !(*this == r); } eMyMoney::Security::Type MyMoneySecurity::securityType() const { Q_D(const MyMoneySecurity); return d->m_securityType; } void MyMoneySecurity::setSecurityType(const eMyMoney::Security::Type s) { Q_D(MyMoneySecurity); d->m_securityType = s; } bool MyMoneySecurity::isCurrency() const { Q_D(const MyMoneySecurity); return d->m_securityType == eMyMoney::Security::Type::Currency; } AlkValue::RoundingMethod MyMoneySecurity::roundingMethod() const { Q_D(const MyMoneySecurity); return d->m_roundingMethod; } void MyMoneySecurity::setRoundingMethod(const AlkValue::RoundingMethod rnd) { Q_D(MyMoneySecurity); d->m_roundingMethod = rnd; } int MyMoneySecurity::smallestAccountFraction() const { Q_D(const MyMoneySecurity); return d->m_smallestAccountFraction; } void MyMoneySecurity::setSmallestAccountFraction(const int sf) { Q_D(MyMoneySecurity); d->m_smallestAccountFraction = sf; } int MyMoneySecurity::smallestCashFraction() const { Q_D(const MyMoneySecurity); return d->m_smallestCashFraction; } void MyMoneySecurity::setSmallestCashFraction(const int cf) { Q_D(MyMoneySecurity); d->m_smallestCashFraction = cf; } int MyMoneySecurity::pricePrecision() const { Q_D(const MyMoneySecurity); return d->m_pricePrecision; } void MyMoneySecurity::setPricePrecision(const int pp) { Q_D(MyMoneySecurity); d->m_pricePrecision = pp; } bool MyMoneySecurity::hasReferenceTo(const QString& id) const { Q_D(const MyMoneySecurity); return (id == d->m_tradingCurrency); } void MyMoneySecurity::writeXML(QDomDocument& document, QDomElement& parent) const { QDomElement el; if (isCurrency()) el = document.createElement(nodeNames[nnCurrency]); else el = document.createElement(nodeNames[nnSecurity]); - writeBaseXML(document, el); - Q_D(const MyMoneySecurity); + d->writeBaseXML(document, el); + el.setAttribute(d->getAttrName(Security::Attribute::Name), d->m_name); el.setAttribute(d->getAttrName(Security::Attribute::Symbol),d->m_tradingSymbol); el.setAttribute(d->getAttrName(Security::Attribute::Type), static_cast(d->m_securityType)); el.setAttribute(d->getAttrName(Security::Attribute::RoundingMethod), static_cast(d->m_roundingMethod)); el.setAttribute(d->getAttrName(Security::Attribute::SAF), d->m_smallestAccountFraction); el.setAttribute(d->getAttrName(Security::Attribute::PP), d->m_pricePrecision); if (isCurrency()) el.setAttribute(d->getAttrName(Security::Attribute::SCF), d->m_smallestCashFraction); else { el.setAttribute(d->getAttrName(Security::Attribute::TradingCurrency), d->m_tradingCurrency); el.setAttribute(d->getAttrName(Security::Attribute::TradingMarket), d->m_tradingMarket); } //Add in Key-Value Pairs for securities. MyMoneyKeyValueContainer::writeXML(document, el); parent.appendChild(el); } QString MyMoneySecurity::securityTypeToString(const eMyMoney::Security::Type securityType) { switch (securityType) { case eMyMoney::Security::Type::Stock: return i18nc("Security type", "Stock"); case eMyMoney::Security::Type::MutualFund: return i18nc("Security type", "Mutual Fund"); case eMyMoney::Security::Type::Bond: return i18nc("Security type", "Bond"); case eMyMoney::Security::Type::Currency: return i18nc("Security type", "Currency"); case eMyMoney::Security::Type::None: return i18nc("Security type", "None"); default: return i18nc("Security type", "Unknown"); } } QString MyMoneySecurity::roundingMethodToString(const AlkValue::RoundingMethod roundingMethod) { switch (roundingMethod) { case AlkValue::RoundNever: return I18N_NOOP("Never"); case AlkValue::RoundFloor: return I18N_NOOP("Floor"); case AlkValue::RoundCeil: return I18N_NOOP("Ceil"); case AlkValue::RoundTruncate: return I18N_NOOP("Truncate"); case AlkValue::RoundPromote: return I18N_NOOP("Promote"); case AlkValue::RoundHalfDown: return I18N_NOOP("HalfDown"); case AlkValue::RoundHalfUp: return I18N_NOOP("HalfUp"); case AlkValue::RoundRound: return I18N_NOOP("Round"); default: return I18N_NOOP("Unknown"); } } diff --git a/kmymoney/mymoney/mymoneysecurity.h b/kmymoney/mymoney/mymoneysecurity.h index 753151bcf..3f5772d8f 100644 --- a/kmymoney/mymoney/mymoneysecurity.h +++ b/kmymoney/mymoney/mymoneysecurity.h @@ -1,192 +1,190 @@ /*************************************************************************** mymoneysecurity.h - description ------------------- begin : Tue Jan 29 2002 copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio (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 MYMONEYSECURITY_H #define MYMONEYSECURITY_H #include "kmm_mymoney_export.h" // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include #include "mymoneyobject.h" #include "mymoneykeyvaluecontainer.h" class QString; namespace eMyMoney { namespace Security { enum class Type; } } /** * Class that holds all the required information about a security that the user * has entered information about. A security can be a stock, a mutual fund, a bond * or a currency. * * @author Kevin Tambascio * @author Thomas Baumgart * @author Łukasz Wojniłowicz */ class MyMoneySecurityPrivate; class KMM_MYMONEY_EXPORT MyMoneySecurity : public MyMoneyObject, public MyMoneyKeyValueContainer { - Q_DECLARE_PRIVATE(MyMoneySecurity) - MyMoneySecurityPrivate* d_ptr; + Q_DECLARE_PRIVATE_D(MyMoneyObject::d_ptr, MyMoneySecurity) KMM_MYMONEY_UNIT_TESTABLE public: MyMoneySecurity(); explicit MyMoneySecurity(const QString& id, const QString& name, const QString& symbol = QString(), const int smallestCashFraction = 100, const int smallestAccountFraction = 100, const int pricePrecision = 4); explicit MyMoneySecurity(const QDomElement& node); MyMoneySecurity(const QString& id, const MyMoneySecurity& other); MyMoneySecurity(const MyMoneySecurity & other); MyMoneySecurity(MyMoneySecurity && other); MyMoneySecurity & operator=(MyMoneySecurity other); friend void swap(MyMoneySecurity& first, MyMoneySecurity& second); ~MyMoneySecurity(); bool operator < (const MyMoneySecurity&) const; /** * This operator tests for equality of two MyMoneySecurity objects */ bool operator == (const MyMoneySecurity&) const; /** * This operator tests for inequality of this MyMoneySecurity object * and the one passed by @p r * * @param r the right side of the comparison */ bool operator != (const MyMoneySecurity& r) const; QString name() const; void setName(const QString& str); QString tradingSymbol() const; void setTradingSymbol(const QString& str); eMyMoney::Security::Type securityType() const; void setSecurityType(const eMyMoney::Security::Type s); bool isCurrency() const; AlkValue::RoundingMethod roundingMethod() const; void setRoundingMethod(const AlkValue::RoundingMethod rnd); QString tradingMarket() const; void setTradingMarket(const QString& str); QString tradingCurrency() const; void setTradingCurrency(const QString& str); int smallestAccountFraction() const; void setSmallestAccountFraction(const int sf); int smallestCashFraction() const; void setSmallestCashFraction(const int cf); int pricePrecision() const; void setPricePrecision(const int pp); void writeXML(QDomDocument& document, QDomElement& parent) const override; /** * This method checks if a reference to the given object exists. It returns, * a @p true if the object is referencing the one requested by the * parameter @p id. If it does not, this method returns @p false. * * @param id id of the object to be checked for references * @retval true This object references object with id @p id. * @retval false This object does not reference the object with id @p id. */ bool hasReferenceTo(const QString& id) const override; /** * This method is used to convert the internal representation of * an security type into a human readable format * * @param securityType enumerated representation of the security type. * For possible values, see MyMoneySecurity::eSECURITYTYPE * * @return QString representing the human readable form */ static QString securityTypeToString(const eMyMoney::Security::Type securityType); /** * This method is used to convert the internal representation of * an rounding method into a human readable format * * @param roundingMethod enumerated representation of the rouding method. * For possible values, see AlkValue::RoundingMethod * * @return QString representing the human readable form */ static QString roundingMethodToString(const AlkValue::RoundingMethod roundingMethod); }; inline void swap(MyMoneySecurity& first, MyMoneySecurity& second) // krazy:exclude=inline { using std::swap; - swap(first.d_ptr, second.d_ptr); - swap(first.m_id, second.m_id); + swap(first.MyMoneyObject::d_ptr, second.MyMoneyObject::d_ptr); swap(first.MyMoneyKeyValueContainer::d_ptr, second.MyMoneyKeyValueContainer::d_ptr); } inline MyMoneySecurity::MyMoneySecurity(MyMoneySecurity && other) : MyMoneySecurity() // krazy:exclude=inline { swap(*this, other); } inline MyMoneySecurity & MyMoneySecurity::operator=(MyMoneySecurity other) // krazy:exclude=inline { swap(*this, other); return *this; } /** * Make it possible to hold @ref MyMoneySecurity objects inside @ref QVariant objects. */ Q_DECLARE_METATYPE(MyMoneySecurity) #endif diff --git a/kmymoney/mymoney/mymoneysecurity_p.h b/kmymoney/mymoney/mymoneysecurity_p.h index 23c57b832..78eb9f6e0 100644 --- a/kmymoney/mymoney/mymoneysecurity_p.h +++ b/kmymoney/mymoney/mymoneysecurity_p.h @@ -1,103 +1,104 @@ /*************************************************************************** mymoneysecurity.cpp - description ------------------- begin : Tue Jan 29 2002 copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio (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 MYMONEYSECURITY_P_H #define MYMONEYSECURITY_P_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes +#include "mymoneyobject_p.h" #include #include "mymoneyenums.h" using namespace eMyMoney; namespace eMyMoney { namespace Security { enum class Attribute { Name = 0, Symbol, Type, RoundingMethod, SAF, PP, SCF, TradingCurrency, TradingMarket, // insert new entries above this line LastAttribute }; uint qHash(const Attribute key, uint seed) { return ::qHash(static_cast(key), seed); } } } -class MyMoneySecurityPrivate { - +class MyMoneySecurityPrivate : public MyMoneyObjectPrivate +{ public: MyMoneySecurityPrivate() : m_securityType(eMyMoney::Security::Type::None), m_smallestCashFraction(100), m_smallestAccountFraction(100), m_pricePrecision(4), m_roundingMethod(AlkValue::RoundRound) { } static QString getAttrName(const Security::Attribute attr) { - static const QHash attrNames = { + static const QHash attrNames { {Security::Attribute::Name, QStringLiteral("name")}, {Security::Attribute::Symbol, QStringLiteral("symbol")}, {Security::Attribute::Type, QStringLiteral("type")}, {Security::Attribute::RoundingMethod, QStringLiteral("rounding-method")}, {Security::Attribute::SAF, QStringLiteral("saf")}, {Security::Attribute::PP, QStringLiteral("pp")}, {Security::Attribute::SCF, QStringLiteral("scf")}, {Security::Attribute::TradingCurrency, QStringLiteral("trading-currency")}, {Security::Attribute::TradingMarket, QStringLiteral("trading-market")} }; return attrNames[attr]; } QString m_name; QString m_tradingSymbol; QString m_tradingMarket; QString m_tradingCurrency; eMyMoney::Security::Type m_securityType; int m_smallestCashFraction; int m_smallestAccountFraction; int m_pricePrecision; AlkValue::RoundingMethod m_roundingMethod; }; #endif diff --git a/kmymoney/mymoney/mymoneysplit.cpp b/kmymoney/mymoney/mymoneysplit.cpp index beda6335d..b3667a393 100644 --- a/kmymoney/mymoney/mymoneysplit.cpp +++ b/kmymoney/mymoney/mymoneysplit.cpp @@ -1,517 +1,512 @@ /*************************************************************************** mymoneysplit.cpp - description ------------------- begin : Sun Apr 28 2002 copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio (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. * * * ***************************************************************************/ #include "mymoneysplit.h" #include "mymoneysplit_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyutils.h" #include "mymoneytransaction.h" const char MyMoneySplit::ActionCheck[] = "Check"; const char MyMoneySplit::ActionDeposit[] = "Deposit"; const char MyMoneySplit::ActionTransfer[] = "Transfer"; const char MyMoneySplit::ActionWithdrawal[] = "Withdrawal"; const char MyMoneySplit::ActionATM[] = "ATM"; const char MyMoneySplit::ActionAmortization[] = "Amortization"; const char MyMoneySplit::ActionInterest[] = "Interest"; const char MyMoneySplit::ActionBuyShares[] = "Buy"; const char MyMoneySplit::ActionDividend[] = "Dividend"; const char MyMoneySplit::ActionReinvestDividend[] = "Reinvest"; const char MyMoneySplit::ActionYield[] = "Yield"; const char MyMoneySplit::ActionAddShares[] = "Add"; const char MyMoneySplit::ActionSplitShares[] = "Split"; const char MyMoneySplit::ActionInterestIncome[] = "IntIncome"; MyMoneySplit::MyMoneySplit() : -d_ptr(new MyMoneySplitPrivate) + MyMoneyObject(*new MyMoneySplitPrivate) { Q_D(MyMoneySplit); d->m_reconcileFlag = eMyMoney::Split::State::NotReconciled; } MyMoneySplit::MyMoneySplit(const QDomElement& node) : - MyMoneyObject(node, false), - MyMoneyKeyValueContainer(node.elementsByTagName(MyMoneySplitPrivate::getElName(Split::Element::KeyValuePairs)).item(0).toElement()), - d_ptr(new MyMoneySplitPrivate) + MyMoneyObject(*new MyMoneySplitPrivate, node, false), + MyMoneyKeyValueContainer(node.elementsByTagName(MyMoneySplitPrivate::getElName(Split::Element::KeyValuePairs)).item(0).toElement()) { Q_D(MyMoneySplit); if (d->getElName(Split::Element::Split) != node.tagName()) throw MYMONEYEXCEPTION("Node was not SPLIT"); clearId(); d->m_payee = MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Split::Attribute::Payee))); QDomNodeList nodeList = node.elementsByTagName(d->getElName(Split::Element::Tag)); for (int i = 0; i < nodeList.count(); i++) d->m_tagList << MyMoneyUtils::QStringEmpty(nodeList.item(i).toElement().attribute(d->getAttrName(Split::Attribute::ID))); d->m_reconcileDate = MyMoneyUtils::stringToDate(MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Split::Attribute::ReconcileDate)))); d->m_action = MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Split::Attribute::Action))); d->m_reconcileFlag = static_cast(node.attribute(d->getAttrName(Split::Attribute::ReconcileFlag)).toInt()); d->m_memo = MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Split::Attribute::Memo))); d->m_value = MyMoneyMoney(MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Split::Attribute::Value)))); d->m_shares = MyMoneyMoney(MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Split::Attribute::Shares)))); d->m_price = MyMoneyMoney(MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Split::Attribute::Price)))); d->m_account = MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Split::Attribute::Account))); d->m_costCenter = MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Split::Attribute::CostCenter))); d->m_number = MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Split::Attribute::Number))); d->m_bankID = MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Split::Attribute::BankID))); } MyMoneySplit::MyMoneySplit(const MyMoneySplit& other) : - MyMoneyObject(other.id()), - MyMoneyKeyValueContainer(other), - d_ptr(new MyMoneySplitPrivate(*other.d_func())) + MyMoneyObject(*new MyMoneySplitPrivate(*other.d_func()), other.id()), + MyMoneyKeyValueContainer(other) { } MyMoneySplit::MyMoneySplit(const QString& id, const MyMoneySplit& other) : - MyMoneyObject(id), - MyMoneyKeyValueContainer(other), - d_ptr(new MyMoneySplitPrivate(*other.d_func())) + MyMoneyObject(*new MyMoneySplitPrivate(*other.d_func()), id), + MyMoneyKeyValueContainer(other) { } MyMoneySplit::~MyMoneySplit() { - Q_D(MyMoneySplit); - delete d; } bool MyMoneySplit::operator == (const MyMoneySplit& right) const { Q_D(const MyMoneySplit); auto d2 = static_cast(right.d_func()); return MyMoneyObject::operator==(right) && MyMoneyKeyValueContainer::operator==(right) && d->m_account == d2->m_account && d->m_costCenter == d2->m_costCenter && d->m_payee == d2->m_payee && d->m_tagList == d2->m_tagList && d->m_memo == d2->m_memo && d->m_action == d2->m_action && d->m_reconcileDate == d2->m_reconcileDate && d->m_reconcileFlag == d2->m_reconcileFlag && ((d->m_number.length() == 0 && d2->m_number.length() == 0) || d->m_number == d2->m_number) && d->m_shares == d2->m_shares && d->m_value == d2->m_value && d->m_price == d2->m_price && d->m_transactionId == d2->m_transactionId; } MyMoneySplit MyMoneySplit::operator-() const { MyMoneySplit rc(*this); rc.d_func()->m_shares = -rc.d_func()->m_shares; rc.d_func()->m_value = -rc.d_func()->m_value; return rc; } QString MyMoneySplit::accountId() const { Q_D(const MyMoneySplit); return d->m_account; } void MyMoneySplit::setAccountId(const QString& account) { Q_D(MyMoneySplit); d->m_account = account; } QString MyMoneySplit::costCenterId() const { Q_D(const MyMoneySplit); return d->m_costCenter; } void MyMoneySplit::setCostCenterId(const QString& costCenter) { Q_D(MyMoneySplit); d->m_costCenter = costCenter; } QString MyMoneySplit::memo() const { Q_D(const MyMoneySplit); return d->m_memo; } void MyMoneySplit::setMemo(const QString& memo) { Q_D(MyMoneySplit); d->m_memo = memo; } eMyMoney::Split::State MyMoneySplit::reconcileFlag() const { Q_D(const MyMoneySplit); return d->m_reconcileFlag; } QDate MyMoneySplit::reconcileDate() const { Q_D(const MyMoneySplit); return d->m_reconcileDate; } void MyMoneySplit::setReconcileDate(const QDate& date) { Q_D(MyMoneySplit); d->m_reconcileDate = date; } void MyMoneySplit::setReconcileFlag(const eMyMoney::Split::State flag) { Q_D(MyMoneySplit); d->m_reconcileFlag = flag; } MyMoneyMoney MyMoneySplit::shares() const { Q_D(const MyMoneySplit); return d->m_shares; } void MyMoneySplit::setShares(const MyMoneyMoney& shares) { Q_D(MyMoneySplit); d->m_shares = shares; } QString MyMoneySplit::value(const QString& key) const { return MyMoneyKeyValueContainer::value(key); } void MyMoneySplit::setValue(const QString& key, const QString& value) { MyMoneyKeyValueContainer::setValue(key, value); } void MyMoneySplit::setValue(const MyMoneyMoney& value) { Q_D(MyMoneySplit); d->m_value = value; } void MyMoneySplit::setValue(const MyMoneyMoney& value, const QString& transactionCurrencyId, const QString& splitCurrencyId) { if (transactionCurrencyId == splitCurrencyId) setValue(value); else setShares(value); } QString MyMoneySplit::payeeId() const { Q_D(const MyMoneySplit); return d->m_payee; } void MyMoneySplit::setPayeeId(const QString& payee) { Q_D(MyMoneySplit); d->m_payee = payee; } QList MyMoneySplit::tagIdList() const { Q_D(const MyMoneySplit); return d->m_tagList; } void MyMoneySplit::setTagIdList(const QList& tagList) { Q_D(MyMoneySplit); d->m_tagList = tagList; } void MyMoneySplit::setAction(eMyMoney::Split::InvestmentTransactionType type) { switch (type) { case eMyMoney::Split::InvestmentTransactionType::BuyShares: case eMyMoney::Split::InvestmentTransactionType::SellShares: setAction(ActionBuyShares); break; case eMyMoney::Split::InvestmentTransactionType::Dividend: setAction(ActionDividend); break; case eMyMoney::Split::InvestmentTransactionType::Yield: setAction(ActionYield); break; case eMyMoney::Split::InvestmentTransactionType::ReinvestDividend: setAction(ActionReinvestDividend); break; case eMyMoney::Split::InvestmentTransactionType::AddShares: case eMyMoney::Split::InvestmentTransactionType::RemoveShares: setAction(ActionAddShares); break; case eMyMoney::Split::InvestmentTransactionType::SplitShares: setAction(ActionSplitShares); break; case eMyMoney::Split::InvestmentTransactionType::InterestIncome: setAction(ActionInterestIncome); break; case eMyMoney::Split::InvestmentTransactionType::UnknownTransactionType: break; } } QString MyMoneySplit::action() const { Q_D(const MyMoneySplit); return d->m_action; } void MyMoneySplit::setAction(const QString& action) { Q_D(MyMoneySplit); d->m_action = action; } bool MyMoneySplit::isAmortizationSplit() const { Q_D(const MyMoneySplit); return d->m_action == ActionAmortization; } bool MyMoneySplit::isInterestSplit() const { Q_D(const MyMoneySplit); return d->m_action == ActionInterest; } QString MyMoneySplit::number() const { Q_D(const MyMoneySplit); return d->m_number; } void MyMoneySplit::setNumber(const QString& number) { Q_D(MyMoneySplit); d->m_number = number; } bool MyMoneySplit::isAutoCalc() const { Q_D(const MyMoneySplit); return (d->m_shares == MyMoneyMoney::autoCalc) || (d->m_value == MyMoneyMoney::autoCalc); } QString MyMoneySplit::bankID() const { Q_D(const MyMoneySplit); return d->m_bankID; } void MyMoneySplit::setBankID(const QString& bankID) { Q_D(MyMoneySplit); d->m_bankID = bankID; } QString MyMoneySplit::transactionId() const { Q_D(const MyMoneySplit); return d->m_transactionId; } void MyMoneySplit::setTransactionId(const QString& id) { Q_D(MyMoneySplit); d->m_transactionId = id; } MyMoneyMoney MyMoneySplit::value() const { Q_D(const MyMoneySplit); return d->m_value; } MyMoneyMoney MyMoneySplit::value(const QString& transactionCurrencyId, const QString& splitCurrencyId) const { Q_D(const MyMoneySplit); return (transactionCurrencyId == splitCurrencyId) ? d->m_value : d->m_shares; } MyMoneyMoney MyMoneySplit::actualPrice() const { Q_D(const MyMoneySplit); return d->m_price; } void MyMoneySplit::setPrice(const MyMoneyMoney& price) { Q_D(MyMoneySplit); d->m_price = price; } MyMoneyMoney MyMoneySplit::price() const { Q_D(const MyMoneySplit); if (!d->m_price.isZero()) return d->m_price; if (!d->m_value.isZero() && !d->m_shares.isZero()) return d->m_value / d->m_shares; return MyMoneyMoney::ONE; } void MyMoneySplit::writeXML(QDomDocument& document, QDomElement& parent) const { Q_D(const MyMoneySplit); - QDomElement el = document.createElement(d->getElName(Split::Element::Split)); + auto el = document.createElement(d->getElName(Split::Element::Split)); - writeBaseXML(document, el); + d->writeBaseXML(document, el); el.setAttribute(d->getAttrName(Split::Attribute::Payee), d->m_payee); //el.setAttribute(getAttrName(Split::Attribute::Tag), m_tag); el.setAttribute(d->getAttrName(Split::Attribute::ReconcileDate), MyMoneyUtils::dateToString(d->m_reconcileDate)); el.setAttribute(d->getAttrName(Split::Attribute::Action), d->m_action); el.setAttribute(d->getAttrName(Split::Attribute::ReconcileFlag), (int)d->m_reconcileFlag); el.setAttribute(d->getAttrName(Split::Attribute::Value), d->m_value.toString()); el.setAttribute(d->getAttrName(Split::Attribute::Shares), d->m_shares.toString()); if (!d->m_price.isZero()) el.setAttribute(d->getAttrName(Split::Attribute::Price), d->m_price.toString()); el.setAttribute(d->getAttrName(Split::Attribute::Memo), d->m_memo); // No need to write the split id as it will be re-assigned when the file is read // el.setAttribute(getAttrName(Split::Attribute::ID), split.id()); el.setAttribute(d->getAttrName(Split::Attribute::Account), d->m_account); el.setAttribute(d->getAttrName(Split::Attribute::Number), d->m_number); el.setAttribute(d->getAttrName(Split::Attribute::BankID), d->m_bankID); if(!d->m_costCenter.isEmpty()) el.setAttribute(d->getAttrName(Split::Attribute::CostCenter), d->m_costCenter); for (int i = 0; i < d->m_tagList.count(); i++) { QDomElement sel = document.createElement(d->getElName(Split::Element::Tag)); sel.setAttribute(d->getAttrName(Split::Attribute::ID), d->m_tagList[i]); el.appendChild(sel); } MyMoneyKeyValueContainer::writeXML(document, el); parent.appendChild(el); } bool MyMoneySplit::hasReferenceTo(const QString& id) const { Q_D(const MyMoneySplit); auto rc = false; if (isMatched()) { rc = matchedTransaction().hasReferenceTo(id); } for (int i = 0; i < d->m_tagList.size(); i++) if (id == d->m_tagList[i]) return true; return rc || (id == d->m_account) || (id == d->m_payee) || (id == d->m_costCenter); } bool MyMoneySplit::isMatched() const { Q_D(const MyMoneySplit); return !(value(d->getAttrName(Split::Attribute::KMMatchedTx)).isEmpty()); } void MyMoneySplit::addMatch(const MyMoneyTransaction& _t) { Q_D(MyMoneySplit); // now we allow matching of two manual transactions if (!isMatched()) { MyMoneyTransaction t(_t); t.clearId(); QDomDocument doc(d->getElName(Split::Element::Match)); QDomElement el = doc.createElement(d->getElName(Split::Element::Container)); doc.appendChild(el); t.writeXML(doc, el); QString xml = doc.toString(); xml.replace('<', "<"); setValue(d->getAttrName(Split::Attribute::KMMatchedTx), xml); } } void MyMoneySplit::removeMatch() { Q_D(MyMoneySplit); deletePair(d->getAttrName(Split::Attribute::KMMatchedTx)); } MyMoneyTransaction MyMoneySplit::matchedTransaction() const { Q_D(const MyMoneySplit); auto xml = value(d->getAttrName(Split::Attribute::KMMatchedTx)); if (!xml.isEmpty()) { xml.replace("<", "<"); QDomDocument doc; QDomElement node; doc.setContent(xml); node = doc.documentElement().firstChild().toElement(); MyMoneyTransaction t(node, false); return t; } return MyMoneyTransaction(); } bool MyMoneySplit::replaceId(const QString& newId, const QString& oldId) { auto changed = false; Q_D(MyMoneySplit); if (d->m_payee == oldId) { d->m_payee = newId; changed = true; } else if (d->m_account == oldId) { d->m_account = newId; changed = true; } else if (d->m_costCenter == oldId) { d->m_costCenter = newId; changed = true; } if (isMatched()) { MyMoneyTransaction t = matchedTransaction(); if (t.replaceId(newId, oldId)) { removeMatch(); addMatch(t); changed = true; } } return changed; } diff --git a/kmymoney/mymoney/mymoneysplit.h b/kmymoney/mymoney/mymoneysplit.h index bcfb731ed..5d53947af 100644 --- a/kmymoney/mymoney/mymoneysplit.h +++ b/kmymoney/mymoney/mymoneysplit.h @@ -1,263 +1,261 @@ /*************************************************************************** mymoneysplit.h - description ------------------- begin : Sun Apr 28 2002 copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio (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 MYMONEYSPLIT_H #define MYMONEYSPLIT_H #include "kmm_mymoney_export.h" // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyobject.h" #include "mymoneykeyvaluecontainer.h" class QString; class QDate; class MyMoneyMoney; class MyMoneyTransaction; namespace eMyMoney { namespace Split { enum class State; enum class InvestmentTransactionType; } } /** * @author Thomas Baumgart * @author Łukasz Wojniłowicz */ /** * This class represents a split of a transaction. */ class MyMoneySplitPrivate; class KMM_MYMONEY_EXPORT MyMoneySplit : public MyMoneyObject, public MyMoneyKeyValueContainer { - Q_DECLARE_PRIVATE(MyMoneySplit) - MyMoneySplitPrivate* d_ptr; + Q_DECLARE_PRIVATE_D(MyMoneyObject::d_ptr, MyMoneySplit) KMM_MYMONEY_UNIT_TESTABLE public: MyMoneySplit(); explicit MyMoneySplit(const QDomElement& node); MyMoneySplit(const QString& id, const MyMoneySplit& other); MyMoneySplit(const MyMoneySplit & other); MyMoneySplit(MyMoneySplit && other); MyMoneySplit & operator=(MyMoneySplit other); friend void swap(MyMoneySplit& first, MyMoneySplit& second); ~MyMoneySplit(); bool operator == (const MyMoneySplit&) const; /** * Returns a copy of the MyMoneySplit where the sign of * shares and value is inverted. */ MyMoneySplit operator-() const; void writeXML(QDomDocument& document, QDomElement& parent) const override; /** * This method checks if a reference to the given object exists. It returns, * a @p true if the object is referencing the one requested by the * parameter @p id. If it does not, this method returns @p false. * * @param id id of the object to be checked for references * @retval true This object references object with id @p id. * @retval false This object does not reference the object with id @p id. */ bool hasReferenceTo(const QString& id) const override; MyMoneyMoney shares() const; void setShares(const MyMoneyMoney& shares); /** * This method returns the price. If the member m_price is not zero * its value is returned. Otherwise, if m_shares is not zero the quotient * of m_value / m_shares is returned. If m_values equals to zero, 1 * will be returned. */ MyMoneyMoney price() const; /** This method just returns what is in m_price, so when we write to the * database, we don't just generate prices */ MyMoneyMoney actualPrice() const; void setPrice(const MyMoneyMoney& price); MyMoneyMoney value() const; MyMoneyMoney value(const QString& transactionCurrencyId, const QString& splitCurrencyId) const; /** * Required to have (direct) access to the MyMoneyKeyValueContainer::value() method. */ QString value(const QString& key) const; /** * Required to have (direct) access to the MyMoneyKeyValueContainer::setValue() method. */ void setValue(const QString& key, const QString& value); void setValue(const MyMoneyMoney& value); /** * This method is used to set either the shares or the value depending on * the currencies assigned to the split/account and the transaction. * * If @p transactionCurrencyId equals @p splitCurrencyId this method * calls setValue(MyMoneyMoney) otherwise setShares(MyMoneyMoney). * * @param value the value to be assiged * @param transactionCurrencyId the id of the currency assigned to the transaction * @param splitCurrencyId the id of the currency assigned to the split (i.e. the * the id of the currency assigned to the account that is * referenced by the split) */ void setValue(const MyMoneyMoney& value, const QString& transactionCurrencyId, const QString& splitCurrencyId); QString accountId() const; void setAccountId(const QString& account); QString costCenterId() const; void setCostCenterId(const QString& costCenter); QString memo() const; void setMemo(const QString& memo); eMyMoney::Split::State reconcileFlag() const; void setReconcileFlag(const eMyMoney::Split::State flag); QDate reconcileDate() const; void setReconcileDate(const QDate& date); QString payeeId() const; void setPayeeId(const QString& payee); QList tagIdList() const; void setTagIdList(const QList& tagList); QString action() const; void setAction(const QString& action); void setAction(eMyMoney::Split::InvestmentTransactionType type); bool isAmortizationSplit() const; bool isInterestSplit() const; QString number() const; void setNumber(const QString& number); bool isAutoCalc() const; QString bankID() const; void setBankID(const QString& bankID); QString transactionId() const; void setTransactionId(const QString& id); /** * returns @a true if this its a transaction matched against an imported * transaction. The imported and matched transaction can be extracted * using matchedTransaction() and can be removed using removeMatch(). */ bool isMatched() const; /** * add an imported transaction @p t as matching transaction. Any previously * added transaction will be overridden. @p t.isImported() must return true, * otherwise the transaction is not stored. */ void addMatch(const MyMoneyTransaction& t); /** * remove the data of the imported transaction added with addMatch(). */ void removeMatch(); /** * Return the matching imported transaction. If no such transaction * is available (isMatched() returns false) an empty transaction is returned. */ MyMoneyTransaction matchedTransaction() const; /** * This method replaces all occurrences of id @a oldId with * @a newId. All other ids are not changed. * * @return true if any change has been performed * @return false if nothing has been modified */ bool replaceId(const QString& newId, const QString& oldId); static const char ActionCheck[]; static const char ActionDeposit[]; static const char ActionTransfer[]; static const char ActionWithdrawal[]; static const char ActionATM[]; static const char ActionAmortization[]; static const char ActionInterest[]; static const char ActionBuyShares[]; // negative amount is sellShares static const char ActionDividend[]; static const char ActionReinvestDividend[]; static const char ActionYield[]; static const char ActionAddShares[]; // negative amount is removeShares static const char ActionSplitShares[]; static const char ActionInterestIncome[]; }; inline void swap(MyMoneySplit& first, MyMoneySplit& second) // krazy:exclude=inline { using std::swap; - swap(first.d_ptr, second.d_ptr); - swap(first.m_id, second.m_id); + swap(first.MyMoneyObject::d_ptr, second.MyMoneyObject::d_ptr); swap(first.MyMoneyKeyValueContainer::d_ptr, second.MyMoneyKeyValueContainer::d_ptr); } inline MyMoneySplit::MyMoneySplit(MyMoneySplit && other) : MyMoneySplit() // krazy:exclude=inline { swap(*this, other); } inline MyMoneySplit & MyMoneySplit::operator=(MyMoneySplit other) // krazy:exclude=inline { swap(*this, other); return *this; } /** * Make it possible to hold @ref MyMoneySplit objects inside @ref QVariant objects. */ Q_DECLARE_METATYPE(MyMoneySplit) #endif diff --git a/kmymoney/mymoney/mymoneysplit_p.h b/kmymoney/mymoney/mymoneysplit_p.h index 800a0d2ca..4bc339150 100644 --- a/kmymoney/mymoney/mymoneysplit_p.h +++ b/kmymoney/mymoney/mymoneysplit_p.h @@ -1,198 +1,204 @@ /*************************************************************************** mymoneysplit.cpp - description ------------------- begin : Sun Apr 28 2002 copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio (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 MYMONEYSPLIT_P_H #define MYMONEYSPLIT_P_H #include "mymoneysplit.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes +#include "mymoneyobject_p.h" #include "mymoneymoney.h" #include "mymoneyenums.h" - -namespace Split +namespace eMyMoney { - enum class Element { Split = 0, - Tag, - Match, - Container, - KeyValuePairs - }; - uint qHash(const Element key, uint seed) { return ::qHash(static_cast(key), seed); } - - enum class Attribute { ID = 0, - BankID, - Account, - Payee, + namespace Split + { + enum class Element { Split = 0, Tag, - Number, - Action, - Value, - Shares, - Price, - Memo, - CostCenter, - ReconcileDate, - ReconcileFlag, - KMMatchedTx, - // insert new entries above this line - LastAttribute + Match, + Container, + KeyValuePairs }; - uint qHash(const Attribute key, uint seed) { return ::qHash(static_cast(key), seed); } + uint qHash(const Element key, uint seed) { return ::qHash(static_cast(key), seed); } + + enum class Attribute { ID = 0, + BankID, + Account, + Payee, + Tag, + Number, + Action, + Value, + Shares, + Price, + Memo, + CostCenter, + ReconcileDate, + ReconcileFlag, + KMMatchedTx, + // insert new entries above this line + LastAttribute + }; + uint qHash(const Attribute key, uint seed) { return ::qHash(static_cast(key), seed); } + } } -class MyMoneySplitPrivate { +using namespace eMyMoney; + +class MyMoneySplitPrivate : public MyMoneyObjectPrivate +{ public: static QString getElName(const Split::Element el) { static const QHash elNames { {Split::Element::Split, QStringLiteral("SPLIT")}, {Split::Element::Tag, QStringLiteral("TAG")}, {Split::Element::Match, QStringLiteral("MATCH")}, {Split::Element::Container, QStringLiteral("CONTAINER")}, {Split::Element::KeyValuePairs, QStringLiteral("KEYVALUEPAIRS")} }; return elNames[el]; } static QString getAttrName(const Split::Attribute attr) { static const QHash attrNames { {Split::Attribute::ID, QStringLiteral("id")}, {Split::Attribute::BankID, QStringLiteral("bankid")}, {Split::Attribute::Account, QStringLiteral("account")}, {Split::Attribute::Payee, QStringLiteral("payee")}, {Split::Attribute::Tag, QStringLiteral("tag")}, {Split::Attribute::Number, QStringLiteral("number")}, {Split::Attribute::Action, QStringLiteral("action")}, {Split::Attribute::Value, QStringLiteral("value")}, {Split::Attribute::Shares, QStringLiteral("shares")}, {Split::Attribute::Price, QStringLiteral("price")}, {Split::Attribute::Memo, QStringLiteral("memo")}, {Split::Attribute::CostCenter, QStringLiteral("costcenter")}, {Split::Attribute::ReconcileDate, QStringLiteral("reconciledate")}, {Split::Attribute::ReconcileFlag, QStringLiteral("reconcileflag")}, {Split::Attribute::KMMatchedTx, QStringLiteral("kmm-matched-tx")} }; return attrNames[attr]; } /** * This member contains the ID of the payee */ QString m_payee; /** * This member contains a list of the IDs of the tags */ QList m_tagList; /** * This member contains the ID of the account */ QString m_account; /** * This member contains the ID of the cost center */ QString m_costCenter; /** */ MyMoneyMoney m_shares; /** */ MyMoneyMoney m_value; /** * If the quotient of m_shares divided by m_values is not the correct price * because of truncation, the price can be stored in this member. For display * purpose and transaction edit this value can be used by the application. */ MyMoneyMoney m_price; QString m_memo; /** * This member contains information about the reconciliation * state of the split. Possible values are * * @li NotReconciled * @li Cleared * @li Reconciled * @li Frozen * */ eMyMoney::Split::State m_reconcileFlag; /** * In case the reconciliation flag is set to Reconciled or Frozen * this member contains the date of the reconciliation. */ QDate m_reconcileDate; /** * The m_action member is an arbitrary string, but is intended to * be conveniently limited to a menu of selections such as * "Buy", "Sell", "Interest", etc. */ QString m_action; /** * The m_number member is used to store a reference number to * the split supplied by the user (e.g. check number, etc.). */ QString m_number; /** * This member keeps the bank's unique ID for the split, so we can * avoid duplicates. This is only used for electronic statement downloads. * * This should only be set on the split which refers to the account * that was downloaded. */ QString m_bankID; /** * This member keeps a backward id to the transaction that this * split can be found in. It is the purpose of the MyMoneyTransaction * object to maintain this member variable. */ QString m_transactionId; }; #endif diff --git a/kmymoney/mymoney/mymoneytag.cpp b/kmymoney/mymoney/mymoneytag.cpp index 66bb7b653..6ed976b3a 100644 --- a/kmymoney/mymoney/mymoneytag.cpp +++ b/kmymoney/mymoney/mymoneytag.cpp @@ -1,180 +1,175 @@ /*************************************************************************** mymoneytag.cpp ------------------- copyright : (C) 2012 by Alessandro Russo (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. * * * ***************************************************************************/ #include "mymoneytag.h" #include "mymoneytag_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyexception.h" #include "mymoneystoragenames.h" using namespace MyMoneyStorageNodes; MyMoneyTag MyMoneyTag::null; MyMoneyTag::MyMoneyTag() : - d_ptr(new MyMoneyTagPrivate) + MyMoneyObject(*new MyMoneyTagPrivate) { } MyMoneyTag::MyMoneyTag(const QString& name) : - d_ptr(new MyMoneyTagPrivate) + MyMoneyObject(*new MyMoneyTagPrivate) { Q_D(MyMoneyTag); d->m_name = name; d->m_tag_color = QColor(); } MyMoneyTag::MyMoneyTag(const QString& name, const QColor& tabColor) : - d_ptr(new MyMoneyTagPrivate) + MyMoneyObject(*new MyMoneyTagPrivate) { Q_D(MyMoneyTag); d->m_name = name; d->m_tag_color = tabColor; } MyMoneyTag::MyMoneyTag(const QDomElement& node) : - MyMoneyObject(node), - d_ptr(new MyMoneyTagPrivate) + MyMoneyObject(*new MyMoneyTagPrivate, node) { if (nodeNames[nnTag] != node.tagName()) { throw MYMONEYEXCEPTION("Node was not TAG"); } Q_D(MyMoneyTag); d->m_name = node.attribute(d->getAttrName(Tag::Attribute::Name)); if (node.hasAttribute(d->getAttrName(Tag::Attribute::TagColor))) { d->m_tag_color.setNamedColor(node.attribute(d->getAttrName(Tag::Attribute::TagColor))); } if (node.hasAttribute(d->getAttrName(Tag::Attribute::Notes))) { d->m_notes = node.attribute(d->getAttrName(Tag::Attribute::Notes)); } d->m_closed = node.attribute(d->getAttrName(Tag::Attribute::Closed), "0").toUInt(); } MyMoneyTag::MyMoneyTag(const MyMoneyTag& other) : - MyMoneyObject(other.id()), - d_ptr(new MyMoneyTagPrivate(*other.d_func())) + MyMoneyObject(*new MyMoneyTagPrivate(*other.d_func()), other.id()) { } MyMoneyTag::MyMoneyTag(const QString& id, const MyMoneyTag& other) : - MyMoneyObject(id), - d_ptr(new MyMoneyTagPrivate(*other.d_func())) + MyMoneyObject(*new MyMoneyTagPrivate(*other.d_func()), id) { Q_D(MyMoneyTag); d->m_tag_color = QColor("black"); } MyMoneyTag::~MyMoneyTag() { - Q_D(MyMoneyTag); - delete d; } QString MyMoneyTag::name() const { Q_D(const MyMoneyTag); return d->m_name; } void MyMoneyTag::setName(const QString& val) { Q_D(MyMoneyTag); d->m_name = val; } bool MyMoneyTag::isClosed() const { Q_D(const MyMoneyTag); return d->m_closed; } void MyMoneyTag::setClosed(bool val) { Q_D(MyMoneyTag); d->m_closed = val; } QColor MyMoneyTag::tagColor() const { Q_D(const MyMoneyTag); return d->m_tag_color; } void MyMoneyTag::setTagColor(const QColor& val) { Q_D(MyMoneyTag); d->m_tag_color = val; } QString MyMoneyTag::notes() const { Q_D(const MyMoneyTag); return d->m_notes; } void MyMoneyTag::setNotes(const QString& val) { Q_D(MyMoneyTag); d->m_notes = val; } bool MyMoneyTag::operator == (const MyMoneyTag& right) const { Q_D(const MyMoneyTag); auto d2 = static_cast(right.d_func()); return (MyMoneyObject::operator==(right) && ((d->m_name.length() == 0 && d2->m_name.length() == 0) || (d->m_name == d2->m_name)) && ((d->m_tag_color.isValid() == false && d2->m_tag_color.isValid() == false) || (d->m_tag_color.name() == d2->m_tag_color.name())) && (d->m_closed == d2->m_closed)); } bool MyMoneyTag::operator < (const MyMoneyTag& right) const { Q_D(const MyMoneyTag); auto d2 = static_cast(right.d_func()); return d->m_name < d2->m_name; } void MyMoneyTag::writeXML(QDomDocument& document, QDomElement& parent) const { auto el = document.createElement(nodeNames[nnTag]); - writeBaseXML(document, el); - Q_D(const MyMoneyTag); + d->writeBaseXML(document, el); + el.setAttribute(d->getAttrName(Tag::Attribute::Name), d->m_name); el.setAttribute(d->getAttrName(Tag::Attribute::Closed), d->m_closed); if (d->m_tag_color.isValid()) el.setAttribute(d->getAttrName(Tag::Attribute::TagColor), d->m_tag_color.name()); if (!d->m_notes.isEmpty()) el.setAttribute(d->getAttrName(Tag::Attribute::Notes), d->m_notes); parent.appendChild(el); } bool MyMoneyTag::hasReferenceTo(const QString& /*id*/) const { return false; } diff --git a/kmymoney/mymoney/mymoneytag.h b/kmymoney/mymoney/mymoneytag.h index 65b235b93..efe5cf5bd 100644 --- a/kmymoney/mymoney/mymoneytag.h +++ b/kmymoney/mymoney/mymoneytag.h @@ -1,134 +1,132 @@ /*************************************************************************** mymoneytag.h ------------------- copyright : (C) 2012 by Alessandro Russo (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 MYMONEYTAG_H #define MYMONEYTAG_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "kmm_mymoney_export.h" #include "mymoneyobject.h" class QString; class QColor; class QDomDocument; /** * This class represents a tag within the MyMoney engine. */ class MyMoneyTagPrivate; class KMM_MYMONEY_EXPORT MyMoneyTag : public MyMoneyObject { Q_DECLARE_PRIVATE(MyMoneyTag) - MyMoneyTagPrivate* d_ptr; KMM_MYMONEY_UNIT_TESTABLE public: MyMoneyTag(); explicit MyMoneyTag(const QString& name); explicit MyMoneyTag(const QString& name, const QColor& tagColor ); /** * This is the constructor for a tag that is described by a * QDomElement (e.g. from a file). * * @param el const reference to the QDomElement from which to * create the object */ explicit MyMoneyTag(const QDomElement& node); MyMoneyTag(const QString& id, const MyMoneyTag& tag); MyMoneyTag(const MyMoneyTag & other); MyMoneyTag(MyMoneyTag && other); MyMoneyTag & operator=(MyMoneyTag other); friend void swap(MyMoneyTag& first, MyMoneyTag& second); ~MyMoneyTag(); QString name() const; void setName(const QString& val); bool isClosed() const; void setClosed(bool val); QColor tagColor() const; void setTagColor(const QColor& val); QString notes() const; void setNotes(const QString& val); // Equality operator bool operator == (const MyMoneyTag &) const; bool operator <(const MyMoneyTag& right) const; void writeXML(QDomDocument& document, QDomElement& parent) const override; /** * This method checks if a reference to the given object exists. It returns, * a @p true if the object is referencing the one requested by the * parameter @p id. If it does not, this method returns @p false. * * @param id id of the object to be checked for references * @retval true This object references object with id @p id. * @retval false This object does not reference the object with id @p id. */ bool hasReferenceTo(const QString& id) const override; static MyMoneyTag null; }; inline void swap(MyMoneyTag& first, MyMoneyTag& second) // krazy:exclude=inline { using std::swap; swap(first.d_ptr, second.d_ptr); - swap(first.m_id, second.m_id); } inline MyMoneyTag::MyMoneyTag(MyMoneyTag && other) : MyMoneyTag() // krazy:exclude=inline { swap(*this, other); } inline MyMoneyTag & MyMoneyTag::operator=(MyMoneyTag other) // krazy:exclude=inline { swap(*this, other); return *this; } //inline bool operator==(const MyMoneyTag& lhs, const QString& rhs) //{ // return lhs.id() == rhs; //} /** * Make it possible to hold @ref MyMoneyTag objects inside @ref QVariant objects. */ Q_DECLARE_METATYPE(MyMoneyTag) #endif diff --git a/kmymoney/mymoney/mymoneytag_p.h b/kmymoney/mymoney/mymoneytag_p.h index 1de93907b..cc5525ded 100644 --- a/kmymoney/mymoney/mymoneytag_p.h +++ b/kmymoney/mymoney/mymoneytag_p.h @@ -1,75 +1,77 @@ /*************************************************************************** mymoneytag.cpp ------------------- copyright : (C) 2012 by Alessandro Russo (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 MYMONEYTAG_P_H #define MYMONEYTAG_P_H // ---------------------------------------------------------------------------- // QT Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes +#include "mymoneyobject_p.h" + namespace Tag { enum class Attribute { Name = 0 , Type, TagColor, Closed, Notes, // insert new entries above this line LastAttribute }; uint qHash(const Attribute key, uint seed) { return ::qHash(static_cast(key), seed); } } -class MyMoneyTagPrivate +class MyMoneyTagPrivate : public MyMoneyObjectPrivate { public: MyMoneyTagPrivate() : m_closed(false) { } static QString getAttrName(const Tag::Attribute attr) { static const QHash attrNames { {Tag::Attribute::Name, QStringLiteral("name")}, {Tag::Attribute::Type, QStringLiteral("type")}, {Tag::Attribute::TagColor, QStringLiteral("tagcolor")}, {Tag::Attribute::Closed, QStringLiteral("closed")}, {Tag::Attribute::Notes, QStringLiteral("notes")}, }; return attrNames[attr]; } // Simple fields QString m_name; // Closed tags will not be shown in the selector inside a transaction, only in the Tag tab bool m_closed; // Set the color showed in the ledger QColor m_tag_color; QString m_notes; }; #endif diff --git a/kmymoney/mymoney/mymoneytransaction.cpp b/kmymoney/mymoney/mymoneytransaction.cpp index 440b1edd6..8e6917b6e 100644 --- a/kmymoney/mymoney/mymoneytransaction.cpp +++ b/kmymoney/mymoney/mymoneytransaction.cpp @@ -1,519 +1,514 @@ /*************************************************************************** mymoneytransaction.cpp ------------------- copyright : (C) 2000 by Michael Edwardes 2002 by Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneytransaction.h" #include "mymoneytransaction_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneystoragenames.h" #include "mymoneyutils.h" #include "mymoneymoney.h" using namespace MyMoneyStorageNodes; MyMoneyTransaction::MyMoneyTransaction() : - MyMoneyObject(), - d_ptr(new MyMoneyTransactionPrivate) + MyMoneyObject(*new MyMoneyTransactionPrivate) { Q_D(MyMoneyTransaction); d->m_nextSplitID = 1; d->m_entryDate = QDate(); d->m_postDate = QDate(); } MyMoneyTransaction::MyMoneyTransaction(const QDomElement& node, const bool forceId) : - MyMoneyObject(node, forceId), - d_ptr(new MyMoneyTransactionPrivate) + MyMoneyObject(*new MyMoneyTransactionPrivate, node, forceId) { Q_D(MyMoneyTransaction); if (nodeNames[nnTransaction] != node.tagName()) throw MYMONEYEXCEPTION("Node was not TRANSACTION"); d->m_nextSplitID = 1; d->m_postDate = MyMoneyUtils::stringToDate(node.attribute(d->getAttrName(Transaction::Attribute::PostDate))); d->m_entryDate = MyMoneyUtils::stringToDate(node.attribute(d->getAttrName(Transaction::Attribute::EntryDate))); d->m_bankID = MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Transaction::Attribute::BankID))); d->m_memo = MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Transaction::Attribute::Memo))); d->m_commodity = MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Transaction::Attribute::Commodity))); QDomNode child = node.firstChild(); while (!child.isNull() && child.isElement()) { QDomElement c = child.toElement(); if (c.tagName() == d->getElName(Transaction::Element::Splits)) { // Process any split information found inside the transaction entry. QDomNodeList nodeList = c.elementsByTagName(d->getElName(Transaction::Element::Split)); for (int i = 0; i < nodeList.count(); ++i) { MyMoneySplit s(nodeList.item(i).toElement()); if (!d->m_bankID.isEmpty()) s.setBankID(d->m_bankID); if (!s.accountId().isEmpty()) addSplit(s); else qDebug("Dropped split because it did not have an account id"); } } else if (c.tagName() == nodeNames[nnKeyValuePairs]) { MyMoneyKeyValueContainer kvp(c); setPairs(kvp.pairs()); } child = child.nextSibling(); } d->m_bankID.clear(); } MyMoneyTransaction::MyMoneyTransaction(const MyMoneyTransaction& other) : - MyMoneyObject(other.id()), - MyMoneyKeyValueContainer(other), - d_ptr(new MyMoneyTransactionPrivate(*other.d_func())) + MyMoneyObject(*new MyMoneyTransactionPrivate(*other.d_func()), other.id()), + MyMoneyKeyValueContainer(other) { } MyMoneyTransaction::MyMoneyTransaction(const QString& id, const MyMoneyTransaction& other) : - MyMoneyObject(id), - MyMoneyKeyValueContainer(other), - d_ptr(new MyMoneyTransactionPrivate(*other.d_func())) + MyMoneyObject(*new MyMoneyTransactionPrivate(*other.d_func()), id), + MyMoneyKeyValueContainer(other) { Q_D(MyMoneyTransaction); if (d->m_entryDate == QDate()) d->m_entryDate = QDate::currentDate(); foreach (auto split, d->m_splits) split.setTransactionId(id); } MyMoneyTransaction::~MyMoneyTransaction() { - Q_D(MyMoneyTransaction); - delete d; } QDate MyMoneyTransaction::entryDate() const { Q_D(const MyMoneyTransaction); return d->m_entryDate; } void MyMoneyTransaction::setEntryDate(const QDate& date) { Q_D(MyMoneyTransaction); d->m_entryDate = date; } QDate MyMoneyTransaction::postDate() const { Q_D(const MyMoneyTransaction); return d->m_postDate; } void MyMoneyTransaction::setPostDate(const QDate& date) { Q_D(MyMoneyTransaction); d->m_postDate = date; } QString MyMoneyTransaction::memo() const { Q_D(const MyMoneyTransaction); return d->m_memo; } void MyMoneyTransaction::setMemo(const QString& memo) { Q_D(MyMoneyTransaction); d->m_memo = memo; } const QList& MyMoneyTransaction::splits() const { Q_D(const MyMoneyTransaction); return d->m_splits; } QList& MyMoneyTransaction::splits() { Q_D(MyMoneyTransaction); return d->m_splits; } uint MyMoneyTransaction::splitCount() const { Q_D(const MyMoneyTransaction); return d->m_splits.count(); } QString MyMoneyTransaction::commodity() const { Q_D(const MyMoneyTransaction); return d->m_commodity; } void MyMoneyTransaction::setCommodity(const QString& commodityId) { Q_D(MyMoneyTransaction); d->m_commodity = commodityId; } QString MyMoneyTransaction::bankID() const { Q_D(const MyMoneyTransaction); return d->m_bankID; } void MyMoneyTransaction::setBankID(const QString& bankID) { Q_D(MyMoneyTransaction); d->m_bankID = bankID; } bool MyMoneyTransaction::operator == (const MyMoneyTransaction& right) const { Q_D(const MyMoneyTransaction); auto d2 = static_cast(right.d_func()); return (MyMoneyObject::operator==(right) && MyMoneyKeyValueContainer::operator==(right) && (d->m_commodity == d2->m_commodity) && ((d->m_memo.length() == 0 && d2->m_memo.length() == 0) || (d->m_memo == d2->m_memo)) && (d->m_splits == d2->m_splits) && (d->m_entryDate == d2->m_entryDate) && (d->m_postDate == d2->m_postDate)); } bool MyMoneyTransaction::operator != (const MyMoneyTransaction& r) const { return !(*this == r); } bool MyMoneyTransaction::operator< (const MyMoneyTransaction& r) const { return postDate() < r.postDate(); } bool MyMoneyTransaction::operator<= (const MyMoneyTransaction& r) const { return postDate() <= r.postDate(); } bool MyMoneyTransaction::operator> (const MyMoneyTransaction& r) const { return postDate() > r.postDate(); } bool MyMoneyTransaction::accountReferenced(const QString& id) const { Q_D(const MyMoneyTransaction); foreach (const auto split, d->m_splits) { if (split.accountId() == id) return true; } return false; } void MyMoneyTransaction::addSplit(MyMoneySplit &split) { if (!split.id().isEmpty()) throw MYMONEYEXCEPTION("Cannot add split with assigned id (" + split.id() + ')'); if (split.accountId().isEmpty()) throw MYMONEYEXCEPTION("Cannot add split that does not contain an account reference"); Q_D(MyMoneyTransaction); MyMoneySplit newSplit(d->nextSplitID(), split); split = newSplit; split.setTransactionId(id()); d->m_splits.append(split); } void MyMoneyTransaction::modifySplit(const MyMoneySplit& split) { // This is the other version which allows having more splits referencing // the same account. if (split.accountId().isEmpty()) throw MYMONEYEXCEPTION("Cannot modify split that does not contain an account reference"); Q_D(MyMoneyTransaction); QList::Iterator it; for (it = d->m_splits.begin(); it != d->m_splits.end(); ++it) { if (split.id() == (*it).id()) { *it = split; return; } } throw MYMONEYEXCEPTION(QString("Invalid split id '%1'").arg(split.id())); } void MyMoneyTransaction::removeSplit(const MyMoneySplit& split) { QList::Iterator it; auto removed = false; Q_D(MyMoneyTransaction); for (it = d->m_splits.begin(); it != d->m_splits.end(); ++it) { if (split.id() == (*it).id()) { d->m_splits.erase(it); removed = true; break; } } if (!removed) throw MYMONEYEXCEPTION(QString("Invalid split id '%1'").arg(split.id())); } void MyMoneyTransaction::removeSplits() { Q_D(MyMoneyTransaction); d->m_splits.clear(); d->m_nextSplitID = 1; } MyMoneySplit MyMoneyTransaction::splitByPayee(const QString& payeeId) const { Q_D(const MyMoneyTransaction); foreach (const auto split, d->m_splits) { if (split.payeeId() == payeeId) return split; } throw MYMONEYEXCEPTION(QString("Split not found for payee '%1'").arg(QString(payeeId))); } MyMoneySplit MyMoneyTransaction::splitByAccount(const QString& accountId, const bool match) const { Q_D(const MyMoneyTransaction); foreach (const auto split, d->m_splits) { if ((match == true && split.accountId() == accountId) || (match == false && split.accountId() != accountId)) return split; } throw MYMONEYEXCEPTION(QString("Split not found for account %1%2").arg(match ? "" : "!").arg(QString(accountId))); } MyMoneySplit MyMoneyTransaction::splitByAccount(const QStringList& accountIds, const bool match) const { Q_D(const MyMoneyTransaction); foreach (const auto split, d->m_splits) { if ((match == true && accountIds.contains(split.accountId())) || (match == false && !accountIds.contains(split.accountId()))) return split; } throw MYMONEYEXCEPTION(QString("Split not found for account %1%1...%2").arg(match ? "" : "!").arg(accountIds.front(), accountIds.back())); } MyMoneySplit MyMoneyTransaction::splitById(const QString& splitId) const { Q_D(const MyMoneyTransaction); foreach (const auto split, d->m_splits) { if (split.id() == splitId) return split; } throw MYMONEYEXCEPTION(QString("Split not found for id '%1'").arg(QString(splitId))); } QString MyMoneyTransaction::firstSplitID() { QString id; id = 'S' + id.setNum(1).rightJustified(MyMoneyTransactionPrivate::SPLIT_ID_SIZE, '0'); return id; } MyMoneyMoney MyMoneyTransaction::splitSum() const { MyMoneyMoney result; Q_D(const MyMoneyTransaction); foreach (const auto split, d->m_splits) result += split.value(); return result; } bool MyMoneyTransaction::isLoanPayment() const { try { Q_D(const MyMoneyTransaction); foreach (const auto split, d->m_splits) { if (split.isAmortizationSplit()) return true; } } catch (const MyMoneyException &) { } return false; } MyMoneySplit MyMoneyTransaction::amortizationSplit() const { static MyMoneySplit nullSplit; Q_D(const MyMoneyTransaction); foreach (const auto split, d->m_splits) { if (split.isAmortizationSplit() && split.isAutoCalc()) return split; } return nullSplit; } MyMoneySplit MyMoneyTransaction::interestSplit() const { static MyMoneySplit nullSplit; Q_D(const MyMoneyTransaction); foreach (const auto split, d->m_splits) { if (split.isInterestSplit() && split.isAutoCalc()) return split; } return nullSplit; } unsigned long MyMoneyTransaction::hash(const QString& txt, unsigned long h) { unsigned long g; for (int i = 0; i < txt.length(); ++i) { unsigned short uc = txt[i].unicode(); for (unsigned j = 0; j < 2; ++j) { unsigned char c = uc & 0xff; // if either the cell or the row of the Unicode char is 0, stop processing if (!c) break; h = (h << 4) + c; if ((g = (h & 0xf0000000))) { h = h ^(g >> 24); h = h ^ g; } uc >>= 8; } } return h; } bool MyMoneyTransaction::isStockSplit() const { Q_D(const MyMoneyTransaction); return (d->m_splits.count() == 1 && d->m_splits[0].action() == MyMoneySplit::ActionSplitShares); } bool MyMoneyTransaction::isImported() const { return value("Imported").toLower() == QString("true"); } void MyMoneyTransaction::setImported(bool state) { if (state) setValue("Imported", "true"); else deletePair("Imported"); } void MyMoneyTransaction::writeXML(QDomDocument& document, QDomElement& parent) const { Q_D(const MyMoneyTransaction); auto el = document.createElement(nodeNames[nnTransaction]); - writeBaseXML(document, el); + d->writeBaseXML(document, el); el.setAttribute(d->getAttrName(Transaction::Attribute::PostDate), MyMoneyUtils::dateToString(d->m_postDate)); el.setAttribute(d->getAttrName(Transaction::Attribute::Memo), d->m_memo); el.setAttribute(d->getAttrName(Transaction::Attribute::EntryDate), MyMoneyUtils::dateToString(d->m_entryDate)); el.setAttribute(d->getAttrName(Transaction::Attribute::Commodity), d->m_commodity); QDomElement splits = document.createElement(d->getElName(Transaction::Element::Splits)); QList::ConstIterator it; for (it = d->m_splits.begin(); it != d->m_splits.end(); ++it) { (*it).writeXML(document, splits); } el.appendChild(splits); MyMoneyKeyValueContainer::writeXML(document, el); parent.appendChild(el); } bool MyMoneyTransaction::hasReferenceTo(const QString& id) const { Q_D(const MyMoneyTransaction); QList::const_iterator it; bool rc = (id == d->m_commodity); for (it = d->m_splits.begin(); rc == false && it != d->m_splits.end(); ++it) { rc = (*it).hasReferenceTo(id); } return rc; } bool MyMoneyTransaction::hasAutoCalcSplit() const { Q_D(const MyMoneyTransaction); foreach (const auto split, d->m_splits) if (split.isAutoCalc()) return true; return false; } QString MyMoneyTransaction::accountSignature(bool includeSplitCount) const { Q_D(const MyMoneyTransaction); QMap accountList; QList::const_iterator it_s; for (it_s = d->m_splits.constBegin(); it_s != d->m_splits.constEnd(); ++it_s) { accountList[(*it_s).accountId()] += 1; } QMap::const_iterator it_a; QString rc; for (it_a = accountList.constBegin(); it_a != accountList.constEnd(); ++it_a) { if (it_a != accountList.constBegin()) rc += '-'; rc += it_a.key(); if (includeSplitCount) rc += QString("*%1").arg(*it_a); } return rc; } QString MyMoneyTransaction::uniqueSortKey() const { + Q_D(const MyMoneyTransaction); QString year, month, day, key; - const QDate& postdate = postDate(); + const auto postdate = postDate(); year = year.setNum(postdate.year()).rightJustified(MyMoneyTransactionPrivate::YEAR_SIZE, '0'); month = month.setNum(postdate.month()).rightJustified(MyMoneyTransactionPrivate::MONTH_SIZE, '0'); day = day.setNum(postdate.day()).rightJustified(MyMoneyTransactionPrivate::DAY_SIZE, '0'); - key = QString(QLatin1String("%1-%2-%3-%4")).arg(year, month, day, m_id); + key = QString::fromLatin1("%1-%2-%3-%4").arg(year, month, day, d->m_id); return key; } bool MyMoneyTransaction::replaceId(const QString& newId, const QString& oldId) { auto changed = false; QList::Iterator it; Q_D(MyMoneyTransaction); for (it = d->m_splits.begin(); it != d->m_splits.end(); ++it) { changed |= (*it).replaceId(newId, oldId); } return changed; } diff --git a/kmymoney/mymoney/mymoneytransaction.h b/kmymoney/mymoney/mymoneytransaction.h index a19978f91..3d5f74721 100644 --- a/kmymoney/mymoney/mymoneytransaction.h +++ b/kmymoney/mymoney/mymoneytransaction.h @@ -1,331 +1,329 @@ /*************************************************************************** mymoneytransaction.h ------------------- copyright : (C) 2000 by Michael Edwardes (C) 2002 by Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MYMONEYTRANSACTION_H #define MYMONEYTRANSACTION_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneykeyvaluecontainer.h" #include "mymoneyobject.h" #include "kmm_mymoney_export.h" #include "mymoneyunittestable.h" class QString; class QDate; class MyMoneyMoney; class MyMoneySplit; class QStringList; template class QList; /** * This class represents a transaction within the MyMoneyEngine. A transaction * contains none, one or more splits of type MyMoneySplit. They are stored in * a QList within this object. A transaction containing only * a single split with an amount not equal to 0 is an unbalanced transaction. It * is tolerated by the engine, but in general not a good idea as it is financially * wrong. */ class MyMoneyTransactionPrivate; class KMM_MYMONEY_EXPORT MyMoneyTransaction : public MyMoneyObject, public MyMoneyKeyValueContainer { - Q_DECLARE_PRIVATE(MyMoneyTransaction) - MyMoneyTransactionPrivate* d_ptr; + Q_DECLARE_PRIVATE_D(MyMoneyObject::d_ptr, MyMoneyTransaction) KMM_MYMONEY_UNIT_TESTABLE public: MyMoneyTransaction(); /** * @param node reference to QDomNode * @param forceId see MyMoneyObject(const QDomElement&, const bool) */ explicit MyMoneyTransaction(const QDomElement& node, const bool forceId = true); MyMoneyTransaction(const QString& id, const MyMoneyTransaction& other); MyMoneyTransaction(const MyMoneyTransaction & other); MyMoneyTransaction(MyMoneyTransaction && other); MyMoneyTransaction & operator=(MyMoneyTransaction other); friend void swap(MyMoneyTransaction& first, MyMoneyTransaction& second); ~MyMoneyTransaction(); friend QDataStream &operator<<(QDataStream &, MyMoneyTransaction &); friend QDataStream &operator>>(QDataStream &, MyMoneyTransaction &); QDate entryDate() const; void setEntryDate(const QDate& date); QDate postDate() const; void setPostDate(const QDate& date); QString memo() const; void setMemo(const QString& memo); const QList& splits() const; QList& splits(); uint splitCount() const; QString commodity() const; void setCommodity(const QString& commodityId); QString bankID() const; void setBankID(const QString& bankID); bool operator == (const MyMoneyTransaction& right) const; bool operator != (const MyMoneyTransaction& r) const; bool operator < (const MyMoneyTransaction& r) const; bool operator <= (const MyMoneyTransaction& r) const; bool operator > (const MyMoneyTransaction& r) const; /** * This method is used to extract a split for a given accountId * from a transaction. A parameter controls, whether the accountId * should match or not. In case of 'not match', the first not-matching * split is returned. * * @param accountId the account to look for * @param match if true, the account Id must match * if false, the account Id must not match * * @return reference to split within the transaction is returned */ MyMoneySplit splitByAccount(const QString& accountId, const bool match = true) const; /** * This method is essentially the same as the previous method, except that * takes a list of accounts instead of just one. * * @param accountIds the list of accounts to look for * @param match if true, the account Id must match * if false, the account Id must not match * * @return reference to split within the transaction is returned */ MyMoneySplit splitByAccount(const QStringList& accountIds, const bool match = true) const; /** * This method is used to extract a split from a transaction. * * @param splitId the split to look for * * @return reference to split within the transaction is returned */ MyMoneySplit splitById(const QString& splitId) const; /** * This method is used to extract a split for a given payeeId * from a transaction. * * @param payeeId the payee to look for * * @return reference to split within the transaction is returned */ MyMoneySplit splitByPayee(const QString& payeeId) const; /** * This method is used to check if the given account is used * in any of the splits of this transaction * * @param id account id that should be checked for usage */ bool accountReferenced(const QString& id) const; /** * This method is used to add a split to the transaction. The split * will be assigned an id. The id member must be empty and the * accountId member must be filled. * * @param split reference to the split that should be added * */ void addSplit(MyMoneySplit &split); /** * This method is used to modify a split in a transaction */ void modifySplit(const MyMoneySplit& split); /** * This method is used to remove a split from a transaction */ void removeSplit(const MyMoneySplit& split); /** * This method is used to remove all splits from a transaction */ void removeSplits(); /** * This method is used to return the sum of all splits of this transaction * * @return MyMoneyMoney value of sum of all splits */ MyMoneyMoney splitSum() const; /** * This method returns information if the transaction * contains information of a loan payment or not. * Loan payment transactions have at least one * split that is identified with a MyMoneySplit::action() of type * MyMoneySplit::ActionAmortization. * * @retval false transaction is no loan payment transaction * @retval true transaction is a loan payment transaction * * @note Upon internal failures, the return value @p false will be used. */ bool isLoanPayment() const; /** * This method returns a const reference to the amortization split. * In case none is found, a reference to an empty split will be returned. */ MyMoneySplit amortizationSplit() const; /** * This method returns a const reference to the interest split. * In case none is found, a reference to an empty split will be returned. */ MyMoneySplit interestSplit() const; /** * returns @a true if this is a stock split transaction */ bool isStockSplit() const; /** * returns @a true if this is an imported transaction */ bool isImported() const; /** * Sets the imported state of this transaction to be the value of @a state . * @p state defaults to @p true. */ void setImported(bool state = true); /** * This static method returns the id which will be assigned to the * first split added to a transaction. This ID can be used to figure * out the split that references the account through which a transaction * was entered. * * @return QString with ID of the first split of transactions */ static QString firstSplitID(); void writeXML(QDomDocument& document, QDomElement& parent) const override; /** * This method checks if a reference to the given object exists. It returns, * a @p true if the object is referencing the one requested by the * parameter @p id. If it does not, this method returns @p false. * * @param id id of the object to be checked for references * @retval true This object references object with id @p id. * @retval false This object does not reference the object with id @p id. */ bool hasReferenceTo(const QString& id) const override; /** * Checks whether any split contains an autocalc split. * * @retval true at least one split has an autocalc value * @retval false all splits have fixed values */ bool hasAutoCalcSplit() const; /** * Returns a signature consisting of the account ids and the * number of times they occur in the transaction if @a includeSplitCount * is @a true. The signature is independent from the order of splits. * * Example: Having splits referencing the account B, A and B, the returned * value will be "A-B" if @p includeSplitCount is @p false or A*1-B*2 if it * is @p true. * * The same result will be returned if the list of splits is A, B, B. * * @param includeSplitCount if @p true, the string @p *n with @p n being * the number of splits referencing this account. The default for * this parameter is @p false. */ QString accountSignature(bool includeSplitCount = false) const; QString uniqueSortKey() const; /** * This module implements an algorithm used by P.J. Weinberger * for fast hashing. Source: COMPILERS by Alfred V. Aho, * pages 435-437. * * It converts the string passed in @p txt into a non-unique * unsigned long integer value. * * @param txt the text to be hashed * @param h initial hash value (default 0) * @return non-unique hash value of the text @p txt */ static unsigned long hash(const QString& txt, unsigned long h = 0); /** * This method replaces all occurrences of id @a oldId with * @a newId. All other ids are not changed. * * @return true if any change has been performed * @return false if nothing has been modified */ bool replaceId(const QString& newId, const QString& oldId); }; inline void swap(MyMoneyTransaction& first, MyMoneyTransaction& second) // krazy:exclude=inline { using std::swap; - swap(first.d_ptr, second.d_ptr); - swap(first.m_id, second.m_id); + swap(first.MyMoneyObject::d_ptr, second.MyMoneyObject::d_ptr); swap(first.MyMoneyKeyValueContainer::d_ptr, second.MyMoneyKeyValueContainer::d_ptr); } inline MyMoneyTransaction::MyMoneyTransaction(MyMoneyTransaction && other) : MyMoneyTransaction() // krazy:exclude=inline { swap(*this, other); } inline MyMoneyTransaction & MyMoneyTransaction::operator=(MyMoneyTransaction other) // krazy:exclude=inline { swap(*this, other); return *this; } /** * Make it possible to hold @ref MyMoneyTransaction objects inside @ref QVariant objects. */ Q_DECLARE_METATYPE(MyMoneyTransaction) #endif diff --git a/kmymoney/mymoney/mymoneytransaction_p.h b/kmymoney/mymoney/mymoneytransaction_p.h index 0e1b9fe72..24b2b6eb5 100644 --- a/kmymoney/mymoney/mymoneytransaction_p.h +++ b/kmymoney/mymoney/mymoneytransaction_p.h @@ -1,139 +1,144 @@ /*************************************************************************** mymoneytransaction.cpp ------------------- copyright : (C) 2000 by Michael Edwardes 2002 by Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MYMONEYTRANSACTION_P_H #define MYMONEYTRANSACTION_P_H // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes +#include "mymoneyobject_p.h" #include "mymoneysplit.h" - -namespace Transaction +namespace eMyMoney { - enum class Element { Split = 0, - Splits }; - uint qHash(const Element key, uint seed) { return ::qHash(static_cast(key), seed); } - - enum class Attribute { Name = 0, - Type, - PostDate, - Memo, - EntryDate, - Commodity, - BankID, - // insert new entries above this line - LastAttribute - }; - - uint qHash(const Attribute key, uint seed) { return ::qHash(static_cast(key), seed); } + namespace Transaction + { + enum class Element { Split = 0, + Splits }; + uint qHash(const Element key, uint seed) { return ::qHash(static_cast(key), seed); } + + enum class Attribute { Name = 0, + Type, + PostDate, + Memo, + EntryDate, + Commodity, + BankID, + // insert new entries above this line + LastAttribute + }; + + uint qHash(const Attribute key, uint seed) { return ::qHash(static_cast(key), seed); } + } } -class MyMoneyTransactionPrivate { +using namespace eMyMoney; +class MyMoneyTransactionPrivate : public MyMoneyObjectPrivate +{ public: static QString getElName(const Transaction::Element el) { static const QHash elNames { {Transaction::Element::Split, QStringLiteral("SPLIT")}, {Transaction::Element::Splits, QStringLiteral("SPLITS")} }; return elNames[el]; } static QString getAttrName(const Transaction::Attribute attr) { static const QHash attrNames { {Transaction::Attribute::Name, QStringLiteral("name")}, {Transaction::Attribute::Type, QStringLiteral("type")}, {Transaction::Attribute::PostDate, QStringLiteral("postdate")}, {Transaction::Attribute::Memo, QStringLiteral("memo")}, {Transaction::Attribute::EntryDate, QStringLiteral("entrydate")}, {Transaction::Attribute::Commodity, QStringLiteral("commodity")}, {Transaction::Attribute::BankID, QStringLiteral("bankid")}, }; return attrNames[attr]; } /** * This method returns the next id to be used for a split */ QString nextSplitID() { QString id; id = 'S' + id.setNum(m_nextSplitID++).rightJustified(SPLIT_ID_SIZE, '0'); return id; } static const int SPLIT_ID_SIZE = 4; /** constants for unique sort key */ static const int YEAR_SIZE = 4; static const int MONTH_SIZE = 2; static const int DAY_SIZE = 2; /** * This member contains the date when the transaction was entered * into the engine */ QDate m_entryDate; /** * This member contains the date the transaction was posted */ QDate m_postDate; /** * This member keeps the memo text associated with this transaction */ QString m_memo; /** * This member contains the splits for this transaction */ QList m_splits; /** * This member keeps the unique numbers of splits within this * transaction. Upon creation of a MyMoneyTransaction object this * value will be set to 1. */ uint m_nextSplitID; /** * This member keeps the base commodity (e.g. currency) for this transaction */ QString m_commodity; /** * This member keeps the bank's unique ID for the transaction, so we can * avoid duplicates. This is only used for electronic statement downloads. * * Note this is now deprecated! Bank ID's should be set on splits, not transactions. */ QString m_bankID; }; #endif diff --git a/kmymoney/mymoney/onlinejob.cpp b/kmymoney/mymoney/onlinejob.cpp index 888abe68a..1255c08f8 100644 --- a/kmymoney/mymoney/onlinejob.cpp +++ b/kmymoney/mymoney/onlinejob.cpp @@ -1,331 +1,323 @@ /* 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 "onlinejob.h" #include "onlinejob_p.h" #include #include #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "tasks/onlinetask.h" #include "onlinejobadministration.h" #include "mymoneystoragenames.h" using namespace MyMoneyStorageNodes; onlineJob::onlineJob() : - MyMoneyObject(), - d_ptr(new onlineJobPrivate), + MyMoneyObject(*new onlineJobPrivate), m_task(0) { Q_D(onlineJob); d->m_jobSend = QDateTime(); d->m_jobBankAnswerDate = QDateTime(); d->m_jobBankAnswerState = noBankAnswer; d->m_messageList = QList(); d->m_locked = false; } onlineJob::onlineJob(onlineTask* task, const QString &id) : - MyMoneyObject(id), - d_ptr(new onlineJobPrivate), + MyMoneyObject(*new onlineJobPrivate, id), m_task(task) { Q_D(onlineJob); d->m_jobSend = QDateTime(); d->m_jobBankAnswerDate = QDateTime(); d->m_jobBankAnswerState = noBankAnswer; d->m_messageList = QList(); d->m_locked = false; } onlineJob::onlineJob(onlineTask* task) : - MyMoneyObject(MyMoneyObject::m_emptyId), - d_ptr(new onlineJobPrivate), + MyMoneyObject(*new onlineJobPrivate, QString()), m_task(task) { Q_D(onlineJob); d->m_jobSend = QDateTime(); d->m_jobBankAnswerDate = QDateTime(); d->m_jobBankAnswerState = noBankAnswer; d->m_messageList = QList(); d->m_locked = false; } onlineJob::onlineJob(onlineJob const& other) : - MyMoneyObject(other.id()), - d_ptr(new onlineJobPrivate(*other.d_func())), + MyMoneyObject(*new onlineJobPrivate(*other.d_func()), other.id()), m_task(0) { copyPointerFromOtherJob(other); } onlineJob::onlineJob(const QString &id, const onlineJob& other) : - MyMoneyObject(id), - d_ptr(new onlineJobPrivate(*other.d_func())), + MyMoneyObject(*new onlineJobPrivate(*other.d_func()), id), m_task() { Q_D(onlineJob); d->m_jobSend = QDateTime(); d->m_jobBankAnswerDate = QDateTime(); d->m_jobBankAnswerState = noBankAnswer; d->m_messageList = QList(); d->m_locked = false; copyPointerFromOtherJob(other); } onlineJob::onlineJob(const QDomElement& element) : - MyMoneyObject(element, true), - d_ptr(new onlineJobPrivate) + MyMoneyObject(*new onlineJobPrivate, element, true) { Q_D(onlineJob); d->m_messageList = QList(); d->m_locked = false; d->m_jobSend = QDateTime::fromString(element.attribute(d->getAttrName(OnlineJob::Attribute::Send)), Qt::ISODate); d->m_jobBankAnswerDate = QDateTime::fromString(element.attribute(d->getAttrName(OnlineJob::Attribute::BankAnswerDate)), Qt::ISODate); QString state = element.attribute(d->getAttrName(OnlineJob::Attribute::BankAnswerState)); if (state == d->getAttrName(OnlineJob::Attribute::AbortedByUser)) d->m_jobBankAnswerState = abortedByUser; else if (state == d->getAttrName(OnlineJob::Attribute::AcceptedByBank)) d->m_jobBankAnswerState = acceptedByBank; else if (state == d->getAttrName(OnlineJob::Attribute::RejectedByBank)) d->m_jobBankAnswerState = rejectedByBank; else if (state == d->getAttrName(OnlineJob::Attribute::SendingError)) d->m_jobBankAnswerState = sendingError; else d->m_jobBankAnswerState = noBankAnswer; QDomElement taskElem = element.firstChildElement(d->getElName(OnlineJob::Element::OnlineTask)); m_task = onlineJobAdministration::instance()->createOnlineTaskByXml(taskElem.attribute(d->getAttrName(OnlineJob::Attribute::IID)), taskElem); } void onlineJob::copyPointerFromOtherJob(const onlineJob &other) { if (!other.isNull()) m_task = other.constTask()->clone(); } void onlineJob::reset() { Q_D(onlineJob); clearId(); d->m_jobSend = QDateTime(); d->m_jobBankAnswerDate = QDateTime(); d->m_jobBankAnswerState = noBankAnswer; d->m_locked = false; } onlineJob::~onlineJob() { - Q_D(onlineJob); - delete d; delete m_task; } onlineTask* onlineJob::task() { if (m_task == 0) throw emptyTask(__FILE__, __LINE__); return m_task; } const onlineTask* onlineJob::task() const { if (m_task == 0) throw emptyTask(__FILE__, __LINE__); return m_task; } const onlineTask* onlineJob::constTask() const { return task(); } QString onlineJob::taskIid() const { try { return task()->taskName(); } catch (const emptyTask&) { } return QString(); } QString onlineJob::responsibleAccount() const { try { return task()->responsibleAccount(); } catch (const emptyTask&) { } return QString(); } MyMoneyAccount onlineJob::responsibleMyMoneyAccount() const { QString accountId = responsibleAccount(); if (!accountId.isEmpty()) return MyMoneyFile::instance()->account(accountId); return MyMoneyAccount(); } bool onlineJob::setLock(bool enable) { Q_D(onlineJob); d->m_locked = enable; return true; } bool onlineJob::isLocked() const { Q_D(const onlineJob); return d->m_locked; } bool onlineJob::isEditable() const { Q_D(const onlineJob); return (!isLocked() && sendDate().isNull() && (d->m_jobBankAnswerState == noBankAnswer || d->m_jobBankAnswerState == sendingError)); } bool onlineJob::isNull() const { return (m_task == 0); } void onlineJob::setJobSend(const QDateTime &dateTime) { Q_D(onlineJob); d->m_jobSend = dateTime; } void onlineJob::setJobSend() { setJobSend(QDateTime::currentDateTime()); } void onlineJob::setBankAnswer(const onlineJob::sendingState sendingState, const QDateTime &dateTime) { Q_D(onlineJob); d->m_jobBankAnswerState = sendingState; d->m_jobBankAnswerDate = dateTime; } void onlineJob::setBankAnswer(const onlineJob::sendingState sendingState) { setBankAnswer(sendingState, QDateTime::currentDateTime()); } QDateTime onlineJob::bankAnswerDate() const { Q_D(const onlineJob); return d->m_jobBankAnswerDate; } onlineJob::sendingState onlineJob::bankAnswerState() const { Q_D(const onlineJob); return d->m_jobBankAnswerState; } void onlineJob::addJobMessage(const onlineJobMessage& message) { Q_D(onlineJob); d->m_messageList.append(message); } void onlineJob::addJobMessage(const eMyMoney::OnlineJob::MessageType& type, const QString& sender, const QString& message, const QString& errorCode, const QDateTime& timestamp) { Q_D(onlineJob); onlineJobMessage logMessage(type, sender, message, timestamp); logMessage.setSenderErrorCode(errorCode); d->m_messageList.append(logMessage); } void onlineJob::addJobMessage(const eMyMoney::OnlineJob::MessageType& type, const QString& sender, const QString& message, const QString& errorCode) { addJobMessage(type, sender, message, errorCode, QDateTime::currentDateTime()); } void onlineJob::addJobMessage(const eMyMoney::OnlineJob::MessageType& type, const QString& sender, const QString& message) { addJobMessage(type, sender, message, QString(), QDateTime::currentDateTime()); } QList onlineJob::jobMessageList() const { Q_D(const onlineJob); return d->m_messageList; } /** @todo give life */ void onlineJob::writeXML(QDomDocument &document, QDomElement &parent) const { - QDomElement el = document.createElement(nodeNames[nnOnlineJob]); - writeBaseXML(document, el); - + auto el = document.createElement(nodeNames[nnOnlineJob]); Q_D(const onlineJob); + d->writeBaseXML(document, el); + if (!d->m_jobSend.isNull()) el.setAttribute(d->getAttrName(OnlineJob::Attribute::Send), d->m_jobSend.toString(Qt::ISODate)); if (!d->m_jobBankAnswerDate.isNull()) el.setAttribute(d->getAttrName(OnlineJob::Attribute::BankAnswerDate), d->m_jobBankAnswerDate.toString(Qt::ISODate)); switch (d->m_jobBankAnswerState) { case abortedByUser: el.setAttribute(d->getAttrName(OnlineJob::Attribute::BankAnswerState), d->getAttrName(OnlineJob::Attribute::AbortedByUser)); break; case acceptedByBank: el.setAttribute(d->getAttrName(OnlineJob::Attribute::BankAnswerState), d->getAttrName(OnlineJob::Attribute::AcceptedByBank)); break; case rejectedByBank: el.setAttribute(d->getAttrName(OnlineJob::Attribute::BankAnswerState), d->getAttrName(OnlineJob::Attribute::RejectedByBank)); break; case sendingError: el.setAttribute(d->getAttrName(OnlineJob::Attribute::BankAnswerState), d->getAttrName(OnlineJob::Attribute::SendingError)); break; case noBankAnswer: default: void(); } QDomElement taskEl = document.createElement(d->getElName(OnlineJob::Element::OnlineTask)); taskEl.setAttribute(d->getAttrName(OnlineJob::Attribute::IID), taskIid()); try { task()->writeXML(document, taskEl); // throws execption if there is no task el.appendChild(taskEl); // only append child if there is something to append } catch (const emptyTask&) { } parent.appendChild(el); } bool onlineJob::isValid() const { if (m_task != 0) return m_task->isValid(); return false; } QDateTime onlineJob::sendDate() const { Q_D(const onlineJob); return d->m_jobSend; } bool onlineJob::hasReferenceTo(const QString& id) const { if (m_task != 0) return m_task->hasReferenceTo(id); return false; } diff --git a/kmymoney/mymoney/onlinejob.h b/kmymoney/mymoney/onlinejob.h index 5c9edd731..1f9a5cecc 100644 --- a/kmymoney/mymoney/onlinejob.h +++ b/kmymoney/mymoney/onlinejob.h @@ -1,358 +1,356 @@ /* 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 . */ #ifndef ONLINEJOB_H #define ONLINEJOB_H #include #include #include "mymoneyobject.h" #include "mymoneyexception.h" #include "onlinejobmessage.h" class onlineTask; class MyMoneyAccount; /** * @brief Class to share jobs which can be procceded by an online banking plugin * * This class stores only the status information and a pointer to an @r onlineTask which stores * the real data. So onlineJob is similar to an shared pointer. * * If you know the type of the onlineTask, @r onlineJobTyped is the first choice to use. * * It is save to use because accesses to pointers (e.g. task() ) throw an execption if onlineJob is null. * * Online jobs are usually not created directly but over @r onlineJobAdministration::createOnlineJob. This is * required to allow loading of onlineTasks at runtime and only if needed. * * This class was created to help writing stable and reliable code. Before an unsafe structure (= pointer) * is accessed it is checked. Exceptions are thrown if the content is unsafe. * * @see onlineTask * @see onlineJobTyped * @todo LOW make data implicitly shared */ class onlineJobPrivate; class KMM_MYMONEY_EXPORT onlineJob : public MyMoneyObject { Q_DECLARE_PRIVATE(onlineJob) - onlineJobPrivate* d_ptr; KMM_MYMONEY_UNIT_TESTABLE public: /** * @brief Contructor for null onlineJobs * * A onlineJob which is null cannot become valid again. * @see isNull() */ onlineJob(); /** * @brief Default construtor * * The onlineJob takes ownership of the task. The task is deleted in the destructor. */ onlineJob(onlineTask* task, const QString& id); // krazy:exclude=explicit onlineJob(onlineTask* task); // krazy:exclude=explicit /** @brief Contruct from xml */ explicit onlineJob(const QDomElement&); /** * @brief Create new onlineJob as copy of other * * This constructor does not copy the status information but the task only. */ onlineJob(const QString &id, const onlineJob& other); onlineJob(const onlineJob & other); onlineJob(onlineJob && other); onlineJob & operator=(onlineJob other); friend void swap(onlineJob& first, onlineJob& second); virtual ~onlineJob(); /** * @brief Returns task attached to this onlineJob * * You should not store this pointer but use onlineJob::task() (or @r onlineJobTyped::task()) * every time you access it. * * @note The return type may change in future (e.g. to an atomic pointer). But you can always expect * the operator @c -> to work like it does for onlineTask*. * * @throws emptyTask if isNull() */ onlineTask* task(); /** @copydoc task(); */ const onlineTask* task() const; /** * @brief Returns task attached to this onlineJob as const * @throws emptyTask if isNull() */ const onlineTask* constTask() const; /** * @brief Returns task of type T attached to this onlineJob * * Internaly a dynamic_cast is done and the result is checked. * * @throws emptyTask if isNull() * @throws badTaskCast if attached task cannot be casted to T */ template T* task(); /** @copydoc task() */ template const T* task() const; template const T* constTask() const { return task(); } template bool canTaskCast() const; QString taskIid() const; /** @todo implement */ bool hasReferenceTo(const QString &id) const override; void writeXML(QDomDocument &document, QDomElement &parent) const override; /** * @brief The state of a job given by the onlinePlugin */ enum sendingState { noBankAnswer, /**< Used during or before sending or if sendDate().isValid() the job was successfully sent */ acceptedByBank, /**< bank definetly confirmed the job */ rejectedByBank, /**< bank definetly rejected this job */ abortedByUser, /**< aborted by user during sending */ sendingError /**< an error occurred, the job is certainly not executed by the bank */ }; /** * @brief Account this job is related to * * Each job must have an account on which the job operates. This is used to determine * the correct onlinePlugin which can execute this job. If the job is related to more * than one account (e.g. a password change) select a random one. * * @return accountId or QString() if none is set or job isNull. */ virtual QString responsibleAccount() const; /** * @brief Returns the MyMoneyAccount this job is related to * @see responsibleAccount() */ MyMoneyAccount responsibleMyMoneyAccount() const; /** * @brief Check if this onlineJob is editable by the user * * A job is no longer editable by the user if it is used for documentary purposes * e.g. the job was sent to the bank. In that case create a new job based on the * old one. * * @todo make it possible to use onlineJobs as templates */ virtual bool isEditable() const; /** * @brief Checks if this onlineJob has an attached task * * @return true if no task is attached to this job */ virtual bool isNull() const; /** * @brief Checks if an valid onlineTask is attached * * @return true if task().isValid(), false if isNull() or !task.isValid() */ virtual bool isValid() const; /** * @brief DateTime the job was sent to the bank * * A valid return does not mean that this job was accepted by the bank. * * @return A valid QDateTime if send to bank, an QDateTime() if not send. */ virtual QDateTime sendDate() const; /** * @brief Mark this job as send * * To be used by online plugin only! * * Set dateTime to QDateTime to mark unsend. */ virtual void setJobSend(const QDateTime &dateTime); virtual void setJobSend(); /** * @brief The bank's answer to this job * * To be used by online plugin only! * * Set dateTime to QDateTime() and bankAnswer to noState to mark unsend. If bankAnswer == noState dateTime.isNull() must be true! */ void setBankAnswer(const sendingState sendingState, const QDateTime &dateTime); void setBankAnswer(const sendingState sendingState); /** * @brief DateTime of the last status update by the bank * */ QDateTime bankAnswerDate() const; /** * @brief Returns last status sand by bank * @return */ sendingState bankAnswerState() const; /** * @brief locks the onlineJob for sending it * * Used when the job is in sending process by the online plugin. * * A locked onlineJob cannot be removed from the storage. * * @note The onlineJob can still be edited and stored. But it should be done by * the one how owns the lock only. * * @todo Enforce the lock somehow? Note: the onlinePlugin must still be able to * write to the job. * * @param enable true locks the job, false unlocks the job */ virtual bool setLock(bool enable = true); /** * @brief Get lock status */ virtual bool isLocked() const; /** * @brief Make this onlineJob a "new" onlineJob * * Removes all status information, log, and the id. Only * the task is keept. */ virtual void reset(); /** * @brief addJobMessage * * To be used by online plugin only. * @param message */ void addJobMessage(const onlineJobMessage &message); /** * @brief Convenient method to set add a log message */ void addJobMessage(const eMyMoney::OnlineJob::MessageType& type, const QString& sender, const QString& message, const QString& errorCode, const QDateTime& timestamp); void addJobMessage(const eMyMoney::OnlineJob::MessageType& type, const QString& sender, const QString& message, const QString& errorCode); void addJobMessage(const eMyMoney::OnlineJob::MessageType& type, const QString& sender, const QString& message); /** * @brief jobMessageList * @return */ virtual QList jobMessageList() const; /** * @brief Thrown if a cast of a task fails * * This is inspired by std::bad_cast */ class badTaskCast : public MyMoneyException { public: explicit badTaskCast(const QString& file = "", const long unsigned int& line = 0) : MyMoneyException("Casted onlineTask with wrong type", file, line) {} }; /** * @brief Thrown if a task of an invalid onlineJob is requested */ class emptyTask : public MyMoneyException { public: explicit emptyTask(const QString& file = "", const long unsigned int& line = 0) : MyMoneyException("Requested onlineTask of onlineJob without any task", file, line) {} }; /** @brief onlineTask attatched to this job */ onlineTask* m_task; private: /** @brief Copies stored pointers (used by copy constructors) */ inline void copyPointerFromOtherJob(const onlineJob& other); }; inline void swap(onlineJob& first, onlineJob& second) // krazy:exclude=inline { using std::swap; swap(first.d_ptr, second.d_ptr); - swap(first.m_id, second.m_id); swap(first.m_task, second.m_task); } inline onlineJob::onlineJob(onlineJob && other) : onlineJob() // krazy:exclude=inline { swap(*this, other); } inline onlineJob & onlineJob::operator=(onlineJob other) // krazy:exclude=inline { swap(*this, other); return *this; } template T* onlineJob::task() { T* ret = dynamic_cast(m_task); if (ret == 0) throw badTaskCast(__FILE__, __LINE__); return ret; } template const T* onlineJob::task() const { const T* ret = dynamic_cast(m_task); if (ret == 0) throw badTaskCast(__FILE__, __LINE__); return ret; } Q_DECLARE_METATYPE(onlineJob) #endif // ONLINEJOB_H diff --git a/kmymoney/mymoney/onlinejob_p.h b/kmymoney/mymoney/onlinejob_p.h index e38a1ed49..72573d6bb 100644 --- a/kmymoney/mymoney/onlinejob_p.h +++ b/kmymoney/mymoney/onlinejob_p.h @@ -1,106 +1,107 @@ /* 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 . */ #ifndef ONLINEJOB_P_H #define ONLINEJOB_P_H #include "onlinejob.h" #include #include #include +#include "mymoneyobject_p.h" #include "onlinejobmessage.h" namespace OnlineJob { enum class Element { OnlineTask }; uint qHash(const Element key, uint seed) { return ::qHash(static_cast(key), seed); } enum class Attribute { Send = 0, BankAnswerDate, BankAnswerState, IID, AbortedByUser, AcceptedByBank, RejectedByBank, SendingError, // insert new entries above this line LastAttribute }; uint qHash(const Attribute key, uint seed) { return ::qHash(static_cast(key), seed); } } -class onlineJobPrivate { - +class onlineJobPrivate : public MyMoneyObjectPrivate +{ public: static QString getElName(const OnlineJob::Element el) { - static const QMap elNames = { + static const QMap elNames { {OnlineJob::Element::OnlineTask, QStringLiteral("onlineTask")} }; return elNames[el]; } static QString getAttrName(const OnlineJob::Attribute attr) { - static const QHash attrNames = { + static const QHash attrNames { {OnlineJob::Attribute::Send, QStringLiteral("send")}, {OnlineJob::Attribute::BankAnswerDate, QStringLiteral("bankAnswerDate")}, {OnlineJob::Attribute::BankAnswerState, QStringLiteral("bankAnswerState")}, {OnlineJob::Attribute::IID, QStringLiteral("iid")}, {OnlineJob::Attribute::AbortedByUser, QStringLiteral("abortedByUser")}, {OnlineJob::Attribute::AcceptedByBank, QStringLiteral("acceptedByBank")}, {OnlineJob::Attribute::RejectedByBank, QStringLiteral("rejectedByBank")}, {OnlineJob::Attribute::SendingError, QStringLiteral("sendingError")}, }; return attrNames[attr]; } /** * @brief Date-time the job was sent to the bank * * This does not mean an answer was given by the bank */ QDateTime m_jobSend; /** * @brief Date-time of confirmation/rejection of the bank * * which state this timestamp belongs to is stored in m_jobBankAnswerState */ QDateTime m_jobBankAnswerDate; /** * @brief Answer of the bank * * combined with m_jobBankAnswerDate */ onlineJob::sendingState m_jobBankAnswerState; /** * @brief Validation result status */ QList m_messageList; /** * @brief Locking state */ bool m_locked; }; #endif diff --git a/kmymoney/mymoney/onlinejobadministration.h b/kmymoney/mymoney/onlinejobadministration.h index ee4bbb14e..88670b02d 100644 --- a/kmymoney/mymoney/onlinejobadministration.h +++ b/kmymoney/mymoney/onlinejobadministration.h @@ -1,343 +1,343 @@ /* * 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 . */ #ifndef ONLINEJOBADMINISTRATION_H #define ONLINEJOBADMINISTRATION_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "onlinejob.h" #include "onlinetasks/interfaces/tasks/onlinetask.h" #include "onlinetasks/interfaces/tasks/ionlinetasksettings.h" #include "onlinetasks/interfaces/tasks/credittransfer.h" #include "onlinetasks/interfaces/converter/onlinetaskconverter.h" class onlineTask; class IonlineJobEdit; class QSqlDatabase; namespace KMyMoneyPlugin { class OnlinePluginExtended; } /** * @brief Connection between KMyMoney and the plugins * * It's main task is the communication with plugins * and caching their information during run-time. During * sending this class selects the correct plugin for each * onlineJob. * * This class keeps an overview which account can handle which job and * offers methods to access these information. * * onlineJobAdministration is created with singleton pattern. Get the * instance with @ref onlineJobAdministration::instance() . */ class KMM_MYMONEY_EXPORT onlineJobAdministration : public QObject { Q_OBJECT KMM_MYMONEY_UNIT_TESTABLE Q_PROPERTY(bool canSendAnyTask READ canSendAnyTask NOTIFY canSendAnyTaskChanged STORED false); Q_PROPERTY(bool canSendCreditTransfer READ canSendCreditTransfer NOTIFY canSendCreditTransferChanged STORED false); public: explicit onlineJobAdministration(QObject *parent = 0); ~onlineJobAdministration(); struct onlineJobEditOffer { QString fileName; QString pluginKeyword; QString name; }; using onlineJobEditOffers = QVector; /** * @brief List all available onlineTasks */ QStringList availableOnlineTasks(); static onlineJobAdministration* instance() { static onlineJobAdministration m_instance; return &m_instance; } /** @brief clear the internal caches for shutdown */ void clearCaches(); /** @brief Use onlineTask::name() to create a corresponding onlineJob */ - onlineJob createOnlineJob(const QString& name, const QString& id = MyMoneyObject::emptyId()) const; + onlineJob createOnlineJob(const QString& name, const QString& id = QString()) const; /** * @brief Return list of IonlineJobEdits * * Method is temporary! * * @return I stay owner of all pointers. */ onlineJobEditOffers onlineJobEdits(); QString onlineJobEditName(onlineJobEditOffer); bool isJobSupported(const QString& accountId, const QString& name) const; bool isJobSupported(const QString& accountId, const QStringList& names) const; bool isAnyJobSupported(const QString& accountId) const; onlineTaskConverter::convertType canConvert(const QString& originalTaskIid, const QString& convertTaskIid) const; onlineTaskConverter::convertType canConvert(const QString& originalTaskIid, const QStringList& convertTaskIids) const; #if 0 template onlineJobTyped convert(const onlineJob& original, const QString& convertTaskIid, onlineTaskConverter::convertType& convertType, QString& userInformation, const QString& onlineJobId) const; template onlineJobTyped convert(const onlineJob& original, const QString& convertTaskIid, onlineTaskConverter::convertType& convertType, QString& userInformation) const; #endif /** * @brief Convert an onlineTask to another type * * @param original onlineJob to convert * @param convertTaskIid onlineTask iid you want to convert into * @param convertType OUT result of conversion. Note: this depends on original * @param userInformation OUT A translated html-string with information about the changes which were done * @param onlineJobId The id of the new onlineJob, if none is given original.id() is used */ onlineJob convert(const onlineJob& original, const QString& convertTaskIid, onlineTaskConverter::convertType& convertType, QString& userInformation, const QString& onlineJobId) const; /** * @copydoc convert() */ onlineJob convert(const onlineJob& original, const QString& convertTaskIid, onlineTaskConverter::convertType& convertType, QString& userInformation) const; /** * @brief Converts a onlineTask to best fitting type of a set of onlineTasks * * Will look for best conversion possible from original to any of convertTaskIids. * * @param original onlineJob to convert * @param convertTaskIids onlineTask-iids you want to convert into. * @param convertType OUT result of conversion. Note: this depends on original * @param userInformation OUT A translated html-string with information about the changes which were done * @param onlineJobId The id of the new onlineJob, if none is given original.id() is used */ onlineJob convertBest(const onlineJob& original, const QStringList& convertTaskIids, onlineTaskConverter::convertType& convertType, QString& userInformation, const QString& onlineJobId) const; /** * @brief Convinient for convertBest() which crates an onlineJob with the same id as original. */ onlineJob convertBest(const onlineJob& original, const QStringList& convertTaskIids, onlineTaskConverter::convertType& convertType, QString& userInformation) const; /** * @brief Request onlineTask::settings from plugin * * @return QSharedPointer to settings from plugin, can be a nullptr */ template QSharedPointer taskSettings(const QString& taskId, const QString& accountId) const; /** * @brief Request onlineTask::settings from plugin * * @see onlineTask::settings * * @param taskId onlineTask::name() * @param accountId MyMoneyAccount.id() * @return QSharedPointer to settings. QSharedPointer::isNull() is true if an error occurs * (e.g. plugin does not support the task). */ QSharedPointer taskSettings(const QString& taskId, const QString& accountId) const; /** * @brief Check if the onlineTask system can do anything * * This is true if at least one plugin can process one of the available onlineTasks for at least one available account. */ bool canSendAnyTask(); /** * @brief Are there plugins and accounts to send a credit transfers? * * Like @r canSendAnyTask() but restricts the onlineTasks to credit transfers. This is useful * to disable the create credit transfer buttons. */ bool canSendCreditTransfer(); /** * @brief Are all preconditions set to edit the given job? */ bool canEditOnlineJob(const onlineJob& job); /** * @brief See if a online task has a specified base * * This is usable if you want to see if e.g. taskIid is * of type creditTransfer */ template bool isInherited(const QString& taskIid) const; Q_SIGNALS: /** * @brief Emitted if canSendAnyTask() changed * * At the moment it this signal can be sent even if the status did not change. */ void canSendAnyTaskChanged(bool); /** * @brief Emitted if canSendCreditTransfer changed * * At the moment it this signal can be sent even if the status did not change. */ void canSendCreditTransferChanged(bool); public Q_SLOTS: void addPlugin(const QString& pluginName, KMyMoneyPlugin::OnlinePluginExtended*); /** * @brief Slot for plugins to make an onlineTask available. * @param task the task to register, I take ownership */ void registerOnlineTask(onlineTask *const task); /** * @brief Slot for plugins to make an onlineTaskConverter available. * @param converter the converter to register, I take ownership */ void registerOnlineTaskConverter(onlineTaskConverter *const converter); /** * @brief Check if the properties about available and sendable online tasks are still valid */ void updateOnlineTaskProperties(); private: /** * @brief Find onlinePlugin which is responsible for accountId * @param accountId * @return Pointer to onlinePluginExtended, do not delete. */ KMyMoneyPlugin::OnlinePluginExtended* getOnlinePlugin(const QString& accountId) const; /** * @brief Creates an onlineTask by iid * @return pointer to task, caller gains ownership. Can be 0. */ onlineTask* createOnlineTask(const QString& iid) const; /** * @brief Creates an onlineTask by its iid and xml data * @return pointer to task, caller gains ownership. Can be 0. */ onlineTask* createOnlineTaskByXml(const QString& iid, const QDomElement& element) const; /** * @brief Creates an onlineTask by its iid and xml data * @return pointer to task, caller gains ownership. Can be 0. */ onlineTask* createOnlineTaskFromSqlDatabase(const QString& iid, const QString& onlineJobId, QSqlDatabase connection) const; // Must be able to call createOnlineTaskByXml friend class onlineJob; // Must be able to call createOnlineTask template friend class onlineJobTyped; // Must be able to call createOnlineTaskFromSqlDatabase() friend class MyMoneyStorageSql; /** * @brief Get root instance of an onlineTask * * Returns a pointer from m_onlineTasks or tries to load/create * a approiate root element. * * Only createOnlineTask and createOnlineTaskByXml use it. * * @return A pointer, you do *not* gain ownership! Can be 0 if something went wrong. * * @internal Made to be forward compatible when onlineTask are loaded as plugins. */ inline onlineTask* rootOnlineTask(const QString& name) const; /** * The key is the onlinePlugin's name */ QMap m_onlinePlugins; /** * The key is the name of the task */ QMap m_onlineTasks; /** * Key is the task the converter converts to */ QMultiMap m_onlineTaskConverter; /** * Intances of editors */ QList m_onlineTaskEditors; }; template QSharedPointer onlineJobAdministration::taskSettings(const QString& taskName, const QString& accountId) const { IonlineTaskSettings::ptr settings = taskSettings(taskName, accountId); if (!settings.isNull()) { QSharedPointer settingsFinal = settings.dynamicCast(); if (Q_LIKELY(!settingsFinal.isNull())) // This can only happen if the onlinePlugin has a bug. return settingsFinal; } return QSharedPointer(); } template< class baseTask > bool onlineJobAdministration::isInherited(const QString& taskIid) const { return (dynamic_cast(rootOnlineTask(taskIid)) != 0); } #if 0 template onlineJobTyped onlineJobAdministration::convert(const onlineJob& original, const QString& convertTaskIid, onlineTaskConverter::convertType& convertType, QString& userInformation, const QString& onlineJobId) const { onlineJob job = convert(original, convertTaskIid, convertType, userInformation, onlineJobId); return onlineJobTyped(job); } template onlineJobTyped< T > onlineJobAdministration::convert(const onlineJob& original, const QString& convertTaskIid, onlineTaskConverter::convertType& convertType, QString& userInformation) const { return convert(original, convertTaskIid, convertType, userInformation, original.id()); } #endif #endif // ONLINEJOBADMINISTRATION_H diff --git a/kmymoney/mymoney/onlinejobtyped.h b/kmymoney/mymoney/onlinejobtyped.h index 650143a8c..be61c3239 100644 --- a/kmymoney/mymoney/onlinejobtyped.h +++ b/kmymoney/mymoney/onlinejobtyped.h @@ -1,148 +1,148 @@ /* * 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 . */ #ifndef ONLINEJOBTYPED_H #define ONLINEJOBTYPED_H #include "onlinejob.h" #include "onlinejobadministration.h" /** * @brief Convenient template if you know the task type of an onlineJob * * To prevent using onlineJob.task() repeatingly you can use this class * where task() has a defined return type. * * Any type check is done in the constructors. So an invalid onlineJobTyped * cannot exist. This class is very fast as well because task() does not need * any checks. * * onlineJobTyped::isNull() is always false. All constructors will throw * onlineJobTyped::badCast or onlineJobTyped::emptyTask if they fail. */ template class onlineJobTyped : public onlineJob { KMM_MYMONEY_UNIT_TESTABLE public: /** * @brief create new task * * @throws emptyTask if plugin could not be found/loaded (determined at runtime). */ explicit onlineJobTyped(); /** * @brief Create typed onlineJob * * @throws emptyTask if task == 0 */ - explicit onlineJobTyped(T* task, const QString& id = MyMoneyObject::m_emptyId); + explicit onlineJobTyped(T* task, const QString& id = QString()); /** @brief Copy constructor */ onlineJobTyped(onlineJobTyped const& other); /** * @brief Copy from onlineJob * * @throws badTaskCast if task in other does not fit T * @throws emptyTask if other has no task */ explicit onlineJobTyped(const onlineJob &other); /** @brief Copy constructor with new id */ explicit onlineJobTyped(const QString &id, const onlineJobTyped& other); /** Does not throw */ inline T* task(); // krazy:exclude=inline /** Does not throw */ inline const T* task() const; // krazy:exclude=inline /** Does not throw */ inline const T* constTask() const { // krazy:exclude=inline return task(); } /** Does not throw */ onlineJobTyped operator =(onlineJobTyped const& other); private: T* m_taskTyped; }; template onlineJobTyped::onlineJobTyped() : onlineJob(onlineJobAdministration::instance()->createOnlineTask(T::name())) { m_taskTyped = static_cast(onlineJob::task()); // this can throw emptyTask // Just be safe: an onlineTask developer could have done something wrong Q_CHECK_PTR(dynamic_cast(onlineJob::task())); } template onlineJobTyped::onlineJobTyped(T* task, const QString& id) : onlineJob(task, id), m_taskTyped(task) { if (task == 0) throw emptyTask(__FILE__, __LINE__); } template onlineJobTyped::onlineJobTyped(onlineJobTyped const& other) : onlineJob(other) { m_taskTyped = dynamic_cast(onlineJob::task()); Q_CHECK_PTR(m_taskTyped); } template onlineJobTyped onlineJobTyped::operator =(onlineJobTyped const & other) { onlineJob::operator =(other); m_taskTyped = dynamic_cast(onlineJob::task()); Q_CHECK_PTR(m_taskTyped); return (*this); } template onlineJobTyped::onlineJobTyped(const onlineJob &other) : onlineJob(other) { m_taskTyped = dynamic_cast(onlineJob::task()); // can throw emptyTask if (m_taskTyped == 0) throw badTaskCast(__FILE__, __LINE__); } template T* onlineJobTyped::task() { Q_CHECK_PTR(m_taskTyped); return m_taskTyped; } template const T* onlineJobTyped::task() const { Q_CHECK_PTR(m_taskTyped); return m_taskTyped; } #endif // ONLINEJOBTYPED_H diff --git a/kmymoney/mymoney/storage/tests/mymoneydatabasemgr-test.cpp b/kmymoney/mymoney/storage/tests/mymoneydatabasemgr-test.cpp index 3cd23f5e4..22f7cf444 100644 --- a/kmymoney/mymoney/storage/tests/mymoneydatabasemgr-test.cpp +++ b/kmymoney/mymoney/storage/tests/mymoneydatabasemgr-test.cpp @@ -1,2354 +1,2356 @@ /*************************************************************************** mymoneydatabasemgrtest.cpp ------------------- copyright : (C) 2008 by Fernando Vilas email : fvilas@iname.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneydatabasemgr-test.h" #include #include #include "mymoneytestutils.h" #include "mymoneymoney.h" #include "mymoneyfile.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneytag.h" #include "mymoneypayee.h" #include "mymoneyschedule.h" +#include "mymoneyschedule_p.h" #include "mymoneyreport.h" #include "mymoneysplit.h" +#include "mymoneysplit_p.h" #include "mymoneytransaction.h" #include "mymoneybudget.h" #include "onlinetasks/dummy/tasks/dummytask.h" #include "misc/platformtools.h" #include "mymoneyenums.h" using namespace eMyMoney; QTEST_GUILESS_MAIN(MyMoneyDatabaseMgrTest) MyMoneyDatabaseMgrTest::MyMoneyDatabaseMgrTest() : m_dbAttached(false), m_canOpen(true), m_haveEmptyDataBase(false), m_file(this), m_emptyFile(this) { // Open and close the temp file so that it exists m_file.open(); m_file.close(); // The same with the empty db file m_emptyFile.open(); m_emptyFile.close(); testCaseTimer.start(); } void MyMoneyDatabaseMgrTest::init() { testStepTimer.start(); m = new MyMoneyDatabaseMgr; // Create file and close it to release possible read-write locks m_file.open(); m_file.close(); } void MyMoneyDatabaseMgrTest::cleanup() { if (m_canOpen) { // All transactions should have already been committed. //m->commitTransaction(); } if (MyMoneyFile::instance()->storageAttached()) { MyMoneyFile::instance()->detachStorage(m); m_dbAttached = false; } delete m; qDebug() << "teststep" << testStepTimer.elapsed() << "msec, total" << testCaseTimer.elapsed() << "msec"; } void MyMoneyDatabaseMgrTest::testEmptyConstructor() { MyMoneyPayee user = m->user(); QVERIFY(user.name().isEmpty()); QVERIFY(user.address().isEmpty()); QVERIFY(user.city().isEmpty()); QVERIFY(user.state().isEmpty()); QVERIFY(user.postcode().isEmpty()); QVERIFY(user.telephone().isEmpty()); QVERIFY(user.email().isEmpty()); QVERIFY(m->nextInstitutionID().isEmpty()); QVERIFY(m->nextAccountID().isEmpty()); QVERIFY(m->nextTransactionID().isEmpty()); QVERIFY(m->nextPayeeID().isEmpty()); QVERIFY(m->nextScheduleID().isEmpty()); QVERIFY(m->nextReportID().isEmpty()); QVERIFY(m->nextOnlineJobID().isEmpty()); QCOMPARE(m->institutionList().count(), 0); QList accList; m->accountList(accList); QCOMPARE(accList.count(), 0); MyMoneyTransactionFilter f; QCOMPARE(m->transactionList(f).count(), 0); QCOMPARE(m->payeeList().count(), 0); QCOMPARE(m->tagList().count(), 0); QCOMPARE(m->scheduleList().count(), 0); QCOMPARE(m->m_creationDate, QDate::currentDate()); } void MyMoneyDatabaseMgrTest::setupUrl(const QString& fname) { QString m_userName = platformTools::osUsername(); QString m_mode = //"QPSQL&mode=single"; //"QMYSQL&mode=single"; "QSQLITE&mode=single"; m_url = QUrl(QString("sql://%1@localhost/%2?driver=%3").arg(m_userName, fname, m_mode)); } void MyMoneyDatabaseMgrTest::copyDatabaseFile(QFile& src, QFile& dest) { if (src.open(QIODevice::ReadOnly)) { if (dest.open(QIODevice::WriteOnly | QIODevice::Truncate)) { dest.write(src.readAll()); dest.close(); } src.close(); } } void MyMoneyDatabaseMgrTest::testBadConnections() { // Check a connection that exists but has empty tables setupUrl(m_file.fileName()); try { QExplicitlySharedDataPointer sql = m->connectToDatabase(m_url); QVERIFY(sql); QEXPECT_FAIL("", "Will fix when correct behaviour in this case is clear.", Continue); QVERIFY(sql->open(m_url, QIODevice::ReadWrite) != 0); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyDatabaseMgrTest::testCreateDb() { try { // Fetch the list of available drivers QStringList list = QSqlDatabase::drivers(); QStringList::Iterator it = list.begin(); if (it == list.end()) { m_canOpen = false; } else { setupUrl(m_file.fileName()); QExplicitlySharedDataPointer sql = m->connectToDatabase(m_url); QVERIFY(0 != sql); //qDebug("Database driver is %s", qPrintable(sql->driverName())); // Clear the database, so there is a fresh start on each run. if (0 == sql->open(m_url, QIODevice::WriteOnly, true)) { MyMoneyFile::instance()->attachStorage(m); QVERIFY(sql->writeFile()); sql->close(); copyDatabaseFile(m_file, m_emptyFile); m_haveEmptyDataBase = true; } else { m_canOpen = false; } } } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyDatabaseMgrTest::testAttachDb() { if (!m_dbAttached) { if (!m_haveEmptyDataBase) { testCreateDb(); } else { // preload database file with empty set copyDatabaseFile(m_emptyFile, m_file); } if (m_canOpen) { try { MyMoneyFile::instance()->detachStorage(); QExplicitlySharedDataPointer sql = m->connectToDatabase(m_url); QVERIFY(sql); int openStatus = sql->open(m_url, QIODevice::ReadWrite); QCOMPARE(openStatus, 0); MyMoneyFile::instance()->attachStorage(m); m_dbAttached = true; } catch (const MyMoneyException &e) { unexpectedException(e); } } } } void MyMoneyDatabaseMgrTest::testDisconnection() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); try { ((QSqlDatabase*)(m->m_sql.data()))->close(); QList accList; m->accountList(accList); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyDatabaseMgrTest::testSetFunctions() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyPayee user = m->user(); user.setName("Name"); m->setUser(user); user.setAddress("Street"); m->setUser(user); user.setCity("Town"); m->setUser(user); user.setState("County"); m->setUser(user); user.setPostcode("Postcode"); m->setUser(user); user.setTelephone("Telephone"); m->setUser(user); user.setEmail("Email"); m->setUser(user); m->setValue("key", "value"); user = m->user(); QVERIFY(user.name() == "Name"); QVERIFY(user.address() == "Street"); QVERIFY(user.city() == "Town"); QVERIFY(user.state() == "County"); QVERIFY(user.postcode() == "Postcode"); QVERIFY(user.telephone() == "Telephone"); QVERIFY(user.email() == "Email"); QVERIFY(m->value("key") == "value"); m->setDirty(); m->deletePair("key"); QVERIFY(m->dirty() == false); } void MyMoneyDatabaseMgrTest::testSupportFunctions() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); try { QCOMPARE(m->nextInstitutionID(), QLatin1String("I000001")); QCOMPARE(m->nextAccountID(), QLatin1String("A000001")); QCOMPARE(m->nextTransactionID(), QLatin1String("T000000000000000001")); QCOMPARE(m->nextPayeeID(), QLatin1String("P000001")); QCOMPARE(m->nextTagID(), QLatin1String("G000001")); QCOMPARE(m->nextScheduleID(), QLatin1String("SCH000001")); QCOMPARE(m->nextReportID(), QLatin1String("R000001")); QCOMPARE(m->nextOnlineJobID(), QLatin1String("O00000001")); QCOMPARE(m->liability().name(), QLatin1String("Liability")); QCOMPARE(m->asset().name(), QLatin1String("Asset")); QCOMPARE(m->expense().name(), QLatin1String("Expense")); QCOMPARE(m->income().name(), QLatin1String("Income")); QCOMPARE(m->equity().name(), QLatin1String("Equity")); QCOMPARE(m->dirty(), false); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyDatabaseMgrTest::testIsStandardAccount() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); QVERIFY(m->isStandardAccount(STD_ACC_LIABILITY) == true); QVERIFY(m->isStandardAccount(STD_ACC_ASSET) == true); QVERIFY(m->isStandardAccount(STD_ACC_EXPENSE) == true); QVERIFY(m->isStandardAccount(STD_ACC_INCOME) == true); QVERIFY(m->isStandardAccount(STD_ACC_EQUITY) == true); QVERIFY(m->isStandardAccount("A0002") == false); } void MyMoneyDatabaseMgrTest::testNewAccount() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyAccount a; a.setName("AccountName"); a.setNumber("AccountNumber"); a.setValue("Key", "Value"); m->addAccount(a); QCOMPARE(m->accountId(), 1ul); QList accList; m->accountList(accList); QCOMPARE(accList.count(), 1); QCOMPARE((*(accList.begin())).name(), QLatin1String("AccountName")); QCOMPARE((*(accList.begin())).id(), QLatin1String("A000001")); QCOMPARE((*(accList.begin())).value("Key"), QLatin1String("Value")); } void MyMoneyDatabaseMgrTest::testAccount() { testNewAccount(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); m->setDirty(); MyMoneyAccount a; // make sure that an invalid ID causes an exception try { a = m->account("Unknown ID"); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } QVERIFY(m->dirty() == false); // now make sure, that a real ID works try { a = m->account("A000001"); QVERIFY(a.name() == "AccountName"); QVERIFY(a.id() == "A000001"); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyDatabaseMgrTest::testAddNewAccount() { testNewAccount(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyAccount a, b; b.setName("Account2"); b.setNumber("Acc2"); m->addAccount(b); m->setDirty(); QVERIFY(m->accountId() == 2); QList accList; m->accountList(accList); QVERIFY(accList.count() == 2); // try to add account to undefined account try { MyMoneyAccount c("UnknownID", b); m->addAccount(c, a); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } QVERIFY(m->dirty() == false); // now try to add account 1 as sub-account to account 2 try { a = m->account("A000001"); QVERIFY(m->asset().accountList().count() == 0); m->addAccount(b, a); MyMoneyAccount acc(m->account("A000002")); QVERIFY(acc.accountList()[0] == "A000001"); QVERIFY(acc.accountList().count() == 1); QVERIFY(m->asset().accountList().count() == 0); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyDatabaseMgrTest::testAddInstitution() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyInstitution i; i.setName("Inst Name"); m->addInstitution(i); QVERIFY(m->institutionList().count() == 1); QVERIFY(m->institutionId() == 1); QVERIFY((*(m->institutionList().begin())).name() == "Inst Name"); QVERIFY((*(m->institutionList().begin())).id() == "I000001"); } void MyMoneyDatabaseMgrTest::testInstitution() { testAddInstitution(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyInstitution i; m->setDirty(); // try to find unknown institution try { i = m->institution("Unknown ID"); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } QVERIFY(m->dirty() == false); // now try to find real institution try { i = m->institution("I000001"); QVERIFY(i.name() == "Inst Name"); QVERIFY(m->dirty() == false); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyDatabaseMgrTest::testAccount2Institution() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddInstitution(); testAddNewAccount(); MyMoneyInstitution i; MyMoneyAccount a, b; try { i = m->institution("I000001"); a = m->account("A000001"); } catch (const MyMoneyException &e) { unexpectedException(e); } m->setDirty(); // try to add to a false institution MyMoneyInstitution fake("Unknown ID", i); a.setInstitutionId(fake.id()); try { m->modifyAccount(a); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } QVERIFY(m->dirty() == false); // now try to do it with a real institution try { QVERIFY(i.accountList().count() == 0); a.setInstitutionId(i.id()); m->modifyAccount(a); QVERIFY(a.institutionId() == i.id()); b = m->account("A000001"); QVERIFY(b.institutionId() == i.id()); QVERIFY(i.accountList().count() == 0); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyDatabaseMgrTest::testModifyAccount() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAccount2Institution(); // test the OK case // //FIXME: modify 2 accounts simultaneously to trip a write error MyMoneyAccount a = m->account("A000001"); a.setName("New account name"); m->setDirty(); try { m->modifyAccount(a); MyMoneyAccount b = m->account("A000001"); QVERIFY(b.parentAccountId() == a.parentAccountId()); QVERIFY(b.name() == "New account name"); QVERIFY(b.value("Key") == "Value"); } catch (const MyMoneyException &e) { unexpectedException(e); } // modify institution to unknown id MyMoneyAccount c("Unknown ID", a); m->setDirty(); try { m->modifyAccount(c); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } // use different account type MyMoneyAccount d; d.setAccountType(Account::Type::CreditCard); MyMoneyAccount f("A000001", d); try { m->modifyAccount(f); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } // use different parent a.setParentAccountId("A000002"); try { m->modifyAccount(c); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } void MyMoneyDatabaseMgrTest::testModifyInstitution() { testAddInstitution(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyInstitution i = m->institution("I000001"); m->setDirty(); i.setName("New inst name"); try { m->modifyInstitution(i); i = m->institution("I000001"); QVERIFY(i.name() == "New inst name"); } catch (const MyMoneyException &e) { unexpectedException(e); } // try to modify an institution that does not exist MyMoneyInstitution f("Unknown ID", i); try { m->modifyInstitution(f); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } void MyMoneyDatabaseMgrTest::testReparentAccount() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); // this one adds some accounts to the database MyMoneyAccount ex1; ex1.setAccountType(Account::Type::Expense); MyMoneyAccount ex2; ex2.setAccountType(Account::Type::Expense); MyMoneyAccount ex3; ex3.setAccountType(Account::Type::Expense); MyMoneyAccount ex4; ex4.setAccountType(Account::Type::Expense); MyMoneyAccount in; in.setAccountType(Account::Type::Income); MyMoneyAccount ch; ch.setAccountType(Account::Type::Checkings); ex1.setName("Sales Tax"); ex2.setName("Sales Tax 16%"); ex3.setName("Sales Tax 7%"); ex4.setName("Grosseries"); in.setName("Salary"); ch.setName("My checkings account"); ch.setValue("Key", "Value"); try { m->addAccount(ex1); m->addAccount(ex2); m->addAccount(ex3); m->addAccount(ex4); m->addAccount(in); m->addAccount(ch); QVERIFY(ex1.id() == "A000001"); QVERIFY(ex2.id() == "A000002"); QVERIFY(ex3.id() == "A000003"); QVERIFY(ex4.id() == "A000004"); QVERIFY(in.id() == "A000005"); QVERIFY(ch.id() == "A000006"); QVERIFY(ch.value("Key") == "Value"); MyMoneyAccount parent = m->expense(); m->addAccount(parent, ex1); m->addAccount(ex1, ex2); m->addAccount(parent, ex3); m->addAccount(parent, ex4); parent = m->income(); m->addAccount(parent, in); parent = m->asset(); m->addAccount(parent, ch); QVERIFY(ch.value("Key") == "Value"); MyMoneyFile::instance()->preloadCache(); QVERIFY(m->expense().accountCount() == 3); QVERIFY(m->account(ex1.id()).accountCount() == 1); QVERIFY(ex3.parentAccountId() == STD_ACC_EXPENSE); //for (int i = 0; i < 100; ++i) { m->reparentAccount(ex3, ex1); //} MyMoneyFile::instance()->preloadCache(); QVERIFY(m->expense().accountCount() == 2); QVERIFY(m->account(ex1.id()).accountCount() == 2); QVERIFY(ex3.parentAccountId() == ex1.id()); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyDatabaseMgrTest::testAddTransactions() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testReparentAccount(); MyMoneyAccount ch; MyMoneyTransaction t1, t2; MyMoneySplit s; try { // I made some money, great s.setAccountId("A000006"); // Checkings s.setShares(MyMoneyMoney(100000, 100)); s.setValue(MyMoneyMoney(100000, 100)); QVERIFY(s.id().isEmpty()); t1.addSplit(s); - s.setId(QString()); // enable re-usage of split variable + s.clearId(); // enable re-usage of split variable s.setAccountId("A000005"); // Salary s.setShares(MyMoneyMoney(-100000, 100)); s.setValue(MyMoneyMoney(-100000, 100)); QVERIFY(s.id().isEmpty()); t1.addSplit(s); t1.setPostDate(QDate(2002, 5, 10)); } catch (const MyMoneyException &e) { unexpectedException(e); } m->setDirty(); try { ch = m->account("A000006"); QVERIFY(ch.value("Key") == "Value"); m->addTransaction(t1); QVERIFY(t1.id() == "T000000000000000001"); QVERIFY(t1.splitCount() == 2); QVERIFY(m->transactionCount() == 1); ch = m->account("A000006"); QVERIFY(ch.value("Key") == "Value"); } catch (const MyMoneyException &e) { unexpectedException(e); } try { // I spent some money, not so great - s.setId(QString()); // enable re-usage of split variable + s.clearId(); // enable re-usage of split variable s.setAccountId("A000004"); // Grosseries s.setShares(MyMoneyMoney(10000, 100)); s.setValue(MyMoneyMoney(10000, 100)); QVERIFY(s.id().isEmpty()); t2.addSplit(s); - s.setId(QString()); // enable re-usage of split variable + s.clearId(); // enable re-usage of split variable s.setAccountId("A000002"); // 16% sales tax s.setShares(MyMoneyMoney(1200, 100)); s.setValue(MyMoneyMoney(1200, 100)); QVERIFY(s.id().isEmpty()); t2.addSplit(s); - s.setId(QString()); // enable re-usage of split variable + s.clearId(); // enable re-usage of split variable s.setAccountId("A000003"); // 7% sales tax s.setShares(MyMoneyMoney(400, 100)); s.setValue(MyMoneyMoney(400, 100)); QVERIFY(s.id().isEmpty()); t2.addSplit(s); - s.setId(QString()); // enable re-usage of split variable + s.clearId(); // enable re-usage of split variable s.setAccountId("A000006"); // Checkings account s.setShares(MyMoneyMoney(-11600, 100)); s.setValue(MyMoneyMoney(-11600, 100)); QVERIFY(s.id().isEmpty()); t2.addSplit(s); t2.setPostDate(QDate(2002, 5, 9)); } catch (const MyMoneyException &e) { unexpectedException(e); } m->setDirty(); try { m->addTransaction(t2); QVERIFY(t2.id() == "T000000000000000002"); QVERIFY(t2.splitCount() == 4); QVERIFY(m->transactionCount() == 2); //QMap::ConstIterator it_k; MyMoneyTransactionFilter f; QList transactionList(m->transactionList(f)); QList::ConstIterator it_t(transactionList.constBegin()); QCOMPARE((*it_t).id(), QLatin1String("T000000000000000002")); ++it_t; QCOMPARE((*it_t).id(), QLatin1String("T000000000000000001")); ++it_t; QCOMPARE(it_t, transactionList.constEnd()); ch = m->account("A000006"); QCOMPARE(ch.value("Key"), QLatin1String("Value")); // check that the account's transaction list is updated QList list; MyMoneyTransactionFilter filter("A000006"); list = m->transactionList(filter); QCOMPARE(list.size(), 2); QList::ConstIterator it; it = list.constBegin(); //QVERIFY((*it).id() == "T000000000000000002"); QCOMPARE((*it), t2); ++it; QCOMPARE((*it), t1); ++it; QCOMPARE(it, list.constEnd()); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyDatabaseMgrTest::testTransactionCount() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddTransactions(); QCOMPARE(m->transactionCount("A000001"), 0u); QCOMPARE(m->transactionCount("A000002"), 1u); QCOMPARE(m->transactionCount("A000003"), 1u); QCOMPARE(m->transactionCount("A000004"), 1u); QCOMPARE(m->transactionCount("A000005"), 1u); QCOMPARE(m->transactionCount("A000006"), 2u); } void MyMoneyDatabaseMgrTest::testAddBudget() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyBudget budget; budget.setName("TestBudget"); budget.setBudgetStart(QDate::currentDate()); m->addBudget(budget); QCOMPARE(m->budgetList().count(), 1); QCOMPARE(m->budgetId(), 1ul); MyMoneyBudget newBudget = m->budgetByName("TestBudget"); QCOMPARE(budget.budgetStart(), newBudget.budgetStart()); QCOMPARE(budget.name(), newBudget.name()); } void MyMoneyDatabaseMgrTest::testCopyBudget() { testAddBudget(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); try { MyMoneyBudget oldBudget = m->budgetByName("TestBudget"); MyMoneyBudget newBudget = oldBudget; newBudget.clearId(); newBudget.setName(QString("Copy of %1").arg(oldBudget.name())); m->addBudget(newBudget); QCOMPARE(m->budgetList().count(), 2); QCOMPARE(m->budgetId(), 2ul); MyMoneyBudget testBudget = m->budgetByName("TestBudget"); QCOMPARE(oldBudget.budgetStart(), testBudget.budgetStart()); QCOMPARE(oldBudget.name(), testBudget.name()); testBudget = m->budgetByName("Copy of TestBudget"); QCOMPARE(testBudget.budgetStart(), newBudget.budgetStart()); QCOMPARE(testBudget.name(), newBudget.name()); } catch (QString& s) { QFAIL(qPrintable(QString("Error in testCopyBudget(): %1").arg(qPrintable(s)))); } } void MyMoneyDatabaseMgrTest::testModifyBudget() { testAddBudget(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyBudget budget = m->budgetByName("TestBudget"); budget.setBudgetStart(QDate::currentDate().addDays(-1)); m->modifyBudget(budget); MyMoneyBudget newBudget = m->budgetByName("TestBudget"); QCOMPARE(budget.id(), newBudget.id()); QCOMPARE(budget.budgetStart(), newBudget.budgetStart()); QCOMPARE(budget.name(), newBudget.name()); } void MyMoneyDatabaseMgrTest::testRemoveBudget() { testAddBudget(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyBudget budget = m->budgetByName("TestBudget"); m->removeBudget(budget); try { budget = m->budgetByName("TestBudget"); // exception should be thrown if budget not found. QFAIL("Missing expected exception."); } catch (const MyMoneyException &) { QVERIFY(true); } } void MyMoneyDatabaseMgrTest::testBalance() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddTransactions(); try { QVERIFY(m->balance("A000001", QDate()).isZero()); QCOMPARE(m->balance("A000002", QDate()), MyMoneyMoney(1200, 100)); QCOMPARE(m->balance("A000003", QDate()), MyMoneyMoney(400, 100)); //Add a transaction to zero account A000003 MyMoneyTransaction t1; MyMoneySplit s; s.setAccountId("A000003"); s.setShares(MyMoneyMoney(-400, 100)); s.setValue(MyMoneyMoney(-400, 100)); QVERIFY(s.id().isEmpty()); t1.addSplit(s); - s.setId(QString()); // enable re-usage of split variable + s.clearId(); // enable re-usage of split variable s.setAccountId("A000002"); s.setShares(MyMoneyMoney(400, 100)); s.setValue(MyMoneyMoney(400, 100)); QVERIFY(s.id().isEmpty()); t1.addSplit(s); t1.setPostDate(QDate(2007, 5, 10)); m->addTransaction(t1); QVERIFY(m->balance("A000003", QDate()).isZero()); QCOMPARE(m->totalBalance("A000001", QDate()), MyMoneyMoney(1600, 100)); QCOMPARE(m->balance("A000006", QDate(2002, 5, 9)), MyMoneyMoney(-11600, 100)); QCOMPARE(m->balance("A000005", QDate(2002, 5, 10)), MyMoneyMoney(-100000, 100)); QCOMPARE(m->balance("A000006", QDate(2002, 5, 10)), MyMoneyMoney(88400, 100)); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyDatabaseMgrTest::testModifyTransaction() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddTransactions(); MyMoneyTransaction t = m->transaction("T000000000000000002"); MyMoneySplit s; MyMoneyAccount ch; // just modify simple stuff (splits) QVERIFY(t.splitCount() == 4); ch = m->account("A000006"); QVERIFY(ch.value("Key") == "Value"); s = t.splits()[0]; s.setShares(MyMoneyMoney(11000, 100)); s.setValue(MyMoneyMoney(11000, 100)); t.modifySplit(s); QVERIFY(t.splitCount() == 4); s = t.splits()[3]; s.setShares(MyMoneyMoney(-12600, 100)); s.setValue(MyMoneyMoney(-12600, 100)); t.modifySplit(s); ch = m->account("A000006"); QVERIFY(ch.value("Key") == "Value"); try { QVERIFY(m->balance("A000004", QDate()) == MyMoneyMoney(10000, 100)); QVERIFY(m->balance("A000006", QDate()) == MyMoneyMoney(100000 - 11600, 100)); QVERIFY(m->totalBalance("A000001", QDate()) == MyMoneyMoney(1600, 100)); m->modifyTransaction(t); ch = m->account("A000006"); QVERIFY(ch.value("Key") == "Value"); QVERIFY(m->balance("A000004", QDate()) == MyMoneyMoney(11000, 100)); QVERIFY(m->balance("A000006", QDate()) == MyMoneyMoney(100000 - 12600, 100)); QVERIFY(m->totalBalance("A000001", QDate()) == MyMoneyMoney(1600, 100)); } catch (const MyMoneyException &e) { unexpectedException(e); } // now modify the date t.setPostDate(QDate(2002, 5, 11)); try { ch = m->account("A000006"); QVERIFY(ch.value("Key") == "Value"); m->modifyTransaction(t); QVERIFY(m->balance("A000004", QDate()) == MyMoneyMoney(11000, 100)); QVERIFY(m->balance("A000006", QDate()) == MyMoneyMoney(100000 - 12600, 100)); QVERIFY(m->totalBalance("A000001", QDate()) == MyMoneyMoney(1600, 100)); //QMap::ConstIterator it_k; MyMoneyTransactionFilter f; QList transactionList(m->transactionList(f)); QList::ConstIterator it_t(transactionList.constBegin()); //it_k = m->m_transactionKeys.begin(); //QVERIFY((*it_k) == "2002-05-10-T000000000000000001"); QVERIFY((*it_t).id() == "T000000000000000001"); //++it_k; ++it_t; //QVERIFY((*it_k) == "2002-05-11-T000000000000000002"); QVERIFY((*it_t).id() == "T000000000000000002"); //++it_k; ++it_t; //QVERIFY(it_k == m->m_transactionKeys.end()); QVERIFY(it_t == transactionList.constEnd()); ch = m->account("A000006"); QVERIFY(ch.value("Key") == "Value"); // check that the account's transaction list is updated QList list; MyMoneyTransactionFilter filter("A000006"); list = m->transactionList(filter); QVERIFY(list.size() == 2); QList::ConstIterator it; it = list.constBegin(); QVERIFY((*it).id() == "T000000000000000001"); ++it; QVERIFY((*it).id() == "T000000000000000002"); ++it; QVERIFY(it == list.constEnd()); } catch (const MyMoneyException &e) { unexpectedException(e); } // Create another transaction MyMoneyTransaction t1; try { - s.setId(QString()); // enable re-usage of split variable + s.clearId(); // enable re-usage of split variable s.setAccountId("A000006"); // Checkings s.setShares(MyMoneyMoney(10000, 100)); s.setValue(MyMoneyMoney(10000, 100)); QVERIFY(s.id().isEmpty()); t1.addSplit(s); - s.setId(QString()); // enable re-usage of split variable + s.clearId(); // enable re-usage of split variable s.setAccountId("A000005"); // Salary s.setShares(MyMoneyMoney(-10000, 100)); s.setValue(MyMoneyMoney(-10000, 100)); QVERIFY(s.id().isEmpty()); t1.addSplit(s); t1.setPostDate(QDate(2002, 5, 10)); } catch (const MyMoneyException &e) { unexpectedException(e); } // Add it to the database m->addTransaction(t1); ch = m->account("A000005"); QVERIFY(ch.balance() == MyMoneyMoney(-100000 - 10000, 100)); QVERIFY(m->balance("A000005", QDate()) == MyMoneyMoney(-100000 - 10000, 100)); ch = m->account("A000006"); QVERIFY(ch.balance() == MyMoneyMoney(100000 - 12600 + 10000, 100)); QVERIFY(m->balance("A000006", QDate()) == MyMoneyMoney(100000 - 12600 + 10000, 100)); // Oops, the income was classified as Salary, but should have been // a refund from the grocery store. t1.splits()[1].setAccountId("A000004"); m->modifyTransaction(t1); // Make sure the account balances got updated correctly. ch = m->account("A000004"); QVERIFY(ch.balance() == MyMoneyMoney(11000 - 10000, 100)); QVERIFY(m->balance("A000004", QDate()) == MyMoneyMoney(11000 - 10000, 100)); ch = m->account("A000005"); QVERIFY(m->balance("A000005", QDate()) == MyMoneyMoney(-100000, 100)); QVERIFY(ch.balance() == MyMoneyMoney(-100000, 100)); ch = m->account("A000006"); QVERIFY(ch.balance() == MyMoneyMoney(100000 - 12600 + 10000, 100)); QVERIFY(m->balance("A000006", QDate()) == MyMoneyMoney(100000 - 12600 + 10000, 100)); } void MyMoneyDatabaseMgrTest::testRemoveUnusedAccount() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAccount2Institution(); MyMoneyAccount a = m->account("A000001"); MyMoneyInstitution i = m->institution("I000001"); m->setDirty(); // make sure, we cannot remove the standard account groups try { m->removeAccount(m->liability()); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } try { m->removeAccount(m->asset()); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } try { m->removeAccount(m->expense()); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } try { m->removeAccount(m->income()); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } // try to remove the account still attached to the institution try { m->removeAccount(a); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } // now really remove an account try { MyMoneyFile::instance()->preloadCache(); i = m->institution("I000001"); //QVERIFY(i.accountCount() == 0); QVERIFY(i.accountCount() == 1); QVERIFY(m->accountCount() == 7); a.setInstitutionId(QString()); m->modifyAccount(a); m->removeAccount(a); QVERIFY(m->accountCount() == 6); i = m->institution("I000001"); QVERIFY(i.accountCount() == 0); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyDatabaseMgrTest::testRemoveUsedAccount() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddTransactions(); MyMoneyAccount a = m->account("A000006"); try { m->removeAccount(a); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } void MyMoneyDatabaseMgrTest::testRemoveInstitution() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testModifyInstitution(); testReparentAccount(); MyMoneyInstitution i; MyMoneyAccount a; // assign the checkings account to the institution try { i = m->institution("I000001"); a = m->account("A000006"); a.setInstitutionId(i.id()); m->modifyAccount(a); QVERIFY(i.accountCount() == 0); } catch (const MyMoneyException &e) { unexpectedException(e); } m->setDirty(); // now remove the institution and see if the account survived ;-) try { m->removeInstitution(i); a.setInstitutionId(QString()); m->modifyAccount(a); a = m->account("A000006"); QVERIFY(a.institutionId().isEmpty()); QVERIFY(m->institutionCount() == 0); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyDatabaseMgrTest::testRemoveTransaction() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddTransactions(); MyMoneyTransaction t = m->transaction("T000000000000000002"); m->setDirty(); try { m->removeTransaction(t); QVERIFY(m->transactionCount() == 1); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyDatabaseMgrTest::testTransactionList() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddTransactions(); QList list; MyMoneyTransactionFilter filter("A000006"); list = m->transactionList(filter); QVERIFY(list.count() == 2); QVERIFY(list.at(0).id() == "T000000000000000002"); QVERIFY(list.at(1).id() == "T000000000000000001"); filter.clear(); filter.addAccount(QString("A000003")); list = m->transactionList(filter); QVERIFY(list.count() == 1); QVERIFY(list.at(0).id() == "T000000000000000002"); filter.clear(); list = m->transactionList(filter); QVERIFY(list.count() == 2); QVERIFY(list.at(0).id() == "T000000000000000002"); QVERIFY(list.at(1).id() == "T000000000000000001"); // test the date filtering while split filtering is active but with an empty filter filter.clear(); filter.addPayee(QString()); filter.setDateFilter(QDate(2002, 5, 10), QDate(2002, 5, 10)); list = m->transactionList(filter); QVERIFY(list.count() == 1); QVERIFY(list.at(0).id() == "T000000000000000001"); filter.clear(); filter.addAccount(QString()); filter.setDateFilter(QDate(2002, 5, 9), QDate(2002, 5, 9)); list = m->transactionList(filter); QVERIFY(list.count() == 1); QVERIFY(list.at(0).id() == "T000000000000000002"); } void MyMoneyDatabaseMgrTest::testAddPayee() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyPayee p; p.setName("THB"); m->setDirty(); try { QVERIFY(m->payeeId() == 0); m->addPayee(p); QVERIFY(m->payeeId() == 1); MyMoneyPayee p1 = m->payeeByName("THB"); QVERIFY(p.id() == p1.id()); QVERIFY(p.name() == p1.name()); QVERIFY(p.address() == p1.address()); QVERIFY(p.city() == p1.city()); QVERIFY(p.state() == p1.state()); QVERIFY(p.postcode() == p1.postcode()); QVERIFY(p.telephone() == p1.telephone()); QVERIFY(p.email() == p1.email()); MyMoneyPayee::payeeMatchType m, m1; bool ignore, ignore1; QStringList keys, keys1; m = p.matchData(ignore, keys); m1 = p1.matchData(ignore1, keys1); QVERIFY(m == m1); QVERIFY(ignore == ignore1); QVERIFY(keys == keys1); QVERIFY(p.reference() == p1.reference()); QVERIFY(p.defaultAccountEnabled() == p1.defaultAccountEnabled()); QVERIFY(p.defaultAccountId() == p1.defaultAccountId()); QVERIFY(p == p1); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyDatabaseMgrTest::testSetAccountName() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); try { m->setAccountName(STD_ACC_LIABILITY, "Verbindlichkeiten"); } catch (const MyMoneyException &e) { unexpectedException(e); } try { m->setAccountName(STD_ACC_ASSET, "Verm�gen"); } catch (const MyMoneyException &e) { unexpectedException(e); } try { m->setAccountName(STD_ACC_EXPENSE, "Ausgaben"); } catch (const MyMoneyException &e) { unexpectedException(e); } try { m->setAccountName(STD_ACC_INCOME, "Einnahmen"); } catch (const MyMoneyException &e) { unexpectedException(e); } MyMoneyFile::instance()->preloadCache(); try { QVERIFY(m->liability().name() == "Verbindlichkeiten"); QVERIFY(m->asset().name() == "Verm�gen"); QVERIFY(m->expense().name() == "Ausgaben"); QVERIFY(m->income().name() == "Einnahmen"); } catch (const MyMoneyException &e) { unexpectedException(e); } try { m->setAccountName("A000001", "New account name"); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } void MyMoneyDatabaseMgrTest::testModifyPayee() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyPayee p; testAddPayee(); p = m->payee("P000001"); p.setName("New name"); m->setDirty(); try { m->modifyPayee(p); p = m->payee("P000001"); QVERIFY(p.name() == "New name"); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyDatabaseMgrTest::testRemovePayee() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddPayee(); m->setDirty(); // check that we can remove an unreferenced payee MyMoneyPayee p = m->payee("P000001"); try { QVERIFY(m->payeeList().count() == 1); m->removePayee(p); QVERIFY(m->payeeList().count() == 0); } catch (const MyMoneyException &e) { unexpectedException(e); } // add transaction testAddTransactions(); MyMoneyTransaction tr = m->transaction("T000000000000000001"); MyMoneySplit sp; sp = tr.splits()[0]; sp.setPayeeId("P000001"); tr.modifySplit(sp); // check that we cannot add a transaction referencing // an unknown payee try { m->modifyTransaction(tr); QFAIL("Expected exception"); } catch (const MyMoneyException &) { } // reset here, so that the // testAddPayee will not fail m->loadPayeeId(0); testAddPayee(); // check that it works when the payee exists try { m->modifyTransaction(tr); } catch (const MyMoneyException &e) { unexpectedException(e); } m->setDirty(); // now check, that we cannot remove the payee try { m->removePayee(p); QFAIL("Expected exception"); } catch (const MyMoneyException &) { } QVERIFY(m->payeeList().count() == 1); } void MyMoneyDatabaseMgrTest::testAddTag() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyTag ta; ta.setName("THB"); m->setDirty(); try { QVERIFY(m->tagId() == 0); m->addTag(ta); QVERIFY(m->tagId() == 1); MyMoneyTag ta1 = m->tagByName("THB"); QVERIFY(ta.id() == ta1.id()); QVERIFY(ta.name() == ta1.name()); QVERIFY(ta.isClosed() == ta1.isClosed()); QVERIFY(ta.tagColor().name() == ta1.tagColor().name()); QVERIFY(ta.notes() == ta1.notes()); QVERIFY(ta == ta1); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyDatabaseMgrTest::testModifyTag() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyTag ta; testAddTag(); ta = m->tag("G000001"); ta.setName("New name"); m->setDirty(); try { m->modifyTag(ta); ta = m->tag("G000001"); QVERIFY(ta.name() == "New name"); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyDatabaseMgrTest::testRemoveTag() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddTag(); m->setDirty(); // check that we can remove an unreferenced tag MyMoneyTag ta = m->tag("G000001"); try { QVERIFY(m->tagList().count() == 1); m->removeTag(ta); QVERIFY(m->tagList().count() == 0); } catch (const MyMoneyException &e) { unexpectedException(e); } // add transaction testAddTransactions(); MyMoneyTransaction tr = m->transaction("T000000000000000001"); MyMoneySplit sp; sp = tr.splits()[0]; QList tagIdList; tagIdList << "G000001"; sp.setTagIdList(tagIdList); tr.modifySplit(sp); // check that we cannot add a transaction referencing // an unknown tag try { m->modifyTransaction(tr); QFAIL("Expected exception"); } catch (const MyMoneyException &) { } // reset here, so that the // testAddTag will not fail m->loadTagId(0); testAddTag(); // check that it works when the tag exists try { m->modifyTransaction(tr); } catch (const MyMoneyException &e) { unexpectedException(e); } m->setDirty(); // now check, that we cannot remove the tag try { m->removeTag(ta); QFAIL("Expected exception"); } catch (const MyMoneyException &) { } QVERIFY(m->tagList().count() == 1); } void MyMoneyDatabaseMgrTest::testRemoveAccountFromTree() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyAccount a, b, c; a.setName("Acc A"); b.setName("Acc B"); c.setName("Acc C"); // build a tree A -> B -> C, remove B and see if A -> C // remains in the storage manager try { m->addAccount(a); m->addAccount(b); m->addAccount(c); m->reparentAccount(b, a); m->reparentAccount(c, b); QVERIFY(a.accountList().count() == 1); QVERIFY(m->account(a.accountList()[0]).name() == "Acc B"); QVERIFY(b.accountList().count() == 1); QVERIFY(m->account(b.accountList()[0]).name() == "Acc C"); QVERIFY(c.accountList().count() == 0); m->removeAccount(b); // reload account info from titutionIDtorage a = m->account(a.id()); c = m->account(c.id()); try { b = m->account(b.id()); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } QVERIFY(a.accountList().count() == 1); QVERIFY(m->account(a.accountList()[0]).name() == "Acc C"); QVERIFY(c.accountList().count() == 0); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyDatabaseMgrTest::testPayeeName() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddPayee(); MyMoneyPayee p; QString name("THB"); // OK case try { p = m->payeeByName(name); QVERIFY(p.name() == "THB"); QVERIFY(p.id() == "P000001"); } catch (const MyMoneyException &e) { unexpectedException(e); } // Not OK case name = "Thb"; try { p = m->payeeByName(name); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } void MyMoneyDatabaseMgrTest::testTagName() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddTag(); MyMoneyTag ta; QString name("THB"); // OK case try { ta = m->tagByName(name); QVERIFY(ta.name() == "THB"); QVERIFY(ta.id() == "G000001"); } catch (const MyMoneyException &e) { unexpectedException(e); } // Not OK case name = "Thb"; try { ta = m->tagByName(name); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } void MyMoneyDatabaseMgrTest::testAssignment() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddTransactions(); MyMoneyPayee user; user.setName("Thomas"); m->setUser(user); MyMoneyDatabaseMgr test = *m; testEquality(&test); } void MyMoneyDatabaseMgrTest::testEquality(const MyMoneyDatabaseMgr *t) { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); QVERIFY(m->user().name() == t->user().name()); QVERIFY(m->user().address() == t->user().address()); QVERIFY(m->user().city() == t->user().city()); QVERIFY(m->user().state() == t->user().state()); QVERIFY(m->user().postcode() == t->user().postcode()); QVERIFY(m->user().telephone() == t->user().telephone()); QVERIFY(m->user().email() == t->user().email()); //QVERIFY(m->nextInstitutionID() == t->nextInstitutionID()); //QVERIFY(m->nextAccountID() == t->nextAccountID()); //QVERIFY(m->m_nextTransactionID == t->m_nextTransactionID); //QVERIFY(m->nextPayeeID() == t->nextPayeeID()); //QVERIFY(m->m_nextScheduleID == t->m_nextScheduleID); QVERIFY(m->dirty() == t->dirty()); QVERIFY(m->m_creationDate == t->m_creationDate); QVERIFY(m->m_lastModificationDate == t->m_lastModificationDate); /* * make sure, that the keys and values are the same * on the left and the right side */ //QVERIFY(m->payeeList().keys() == t->payeeList().keys()); //QVERIFY(m->payeeList().values() == t->payeeList().values()); QVERIFY(m->payeeList() == t->payeeList()); QVERIFY(m->tagList() == t->tagList()); //QVERIFY(m->m_transactionKeys.keys() == t->m_transactionKeys.keys()); //QVERIFY(m->m_transactionKeys.values() == t->m_transactionKeys.values()); //QVERIFY(m->institutionList().keys() == t->institutionList().keys()); //QVERIFY(m->institutionList().values() == t->institutionList().values()); //QVERIFY(m->m_accountList.keys() == t->m_accountList.keys()); //QVERIFY(m->m_accountList.values() == t->m_accountList.values()); //QVERIFY(m->m_transactionList.keys() == t->m_transactionList.keys()); //QVERIFY(m->m_transactionList.values() == t->m_transactionList.values()); //QVERIFY(m->m_balanceCache.keys() == t->m_balanceCache.keys()); //QVERIFY(m->m_balanceCache.values() == t->m_balanceCache.values()); // QVERIFY(m->scheduleList().keys() == t->scheduleList().keys()); // QVERIFY(m->scheduleList().values() == t->scheduleList().values()); } void MyMoneyDatabaseMgrTest::testDuplicate() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); const MyMoneyDatabaseMgr* t; testModifyTransaction(); t = m->duplicate(); testEquality(t); delete t; } void MyMoneyDatabaseMgrTest::testAddSchedule() { /* Note addSchedule() now calls validate as it should * so we need an account id. Later this will * be checked to make sure its a valid account id. The * tests currently fail because no splits are defined * for the schedules transaction. */ testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); // put some accounts in the db, so the tests don't break testReparentAccount(); try { QVERIFY(m->scheduleList().count() == 0); MyMoneyTransaction t1; MyMoneySplit s1, s2; s1.setAccountId("A000001"); t1.addSplit(s1); s2.setAccountId("A000002"); t1.addSplit(s2); MyMoneySchedule schedule("Sched-Name", Schedule::Type::Deposit, Schedule::Occurrence::Daily, 1, Schedule::PaymentType::ManualDeposit, QDate(), QDate(), true, false); t1.setPostDate(QDate(2003, 7, 10)); schedule.setTransaction(t1); m->addSchedule(schedule); QVERIFY(m->scheduleList().count() == 1); QVERIFY(schedule.id() == "SCH000001"); //MyMoneyFile::instance()->clearCache(); // test passes without this, so why is it here for? QVERIFY(m->schedule("SCH000001").id() == "SCH000001"); } catch (const MyMoneyException &e) { unexpectedException(e); } try { MyMoneySchedule schedule("Sched-Name", Schedule::Type::Deposit, Schedule::Occurrence::Daily, 1, Schedule::PaymentType::ManualDeposit, QDate(), QDate(), true, false); m->addSchedule(schedule); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } QVERIFY(m->scheduleList().count() == 1); // now try with a bad account, so this should cause an exception try { MyMoneyTransaction t1; MyMoneySplit s1, s2; s1.setAccountId("Abadaccount1"); t1.addSplit(s1); s2.setAccountId("Abadaccount2"); //t1.addSplit(s2); MyMoneySchedule schedule("Sched-Name", Schedule::Type::Deposit, Schedule::Occurrence::Daily, 1, Schedule::PaymentType::ManualDeposit, QDate(), QDate(), true, false); t1.setPostDate(QDate(2003, 7, 10)); schedule.setTransaction(t1); m->addSchedule(schedule); QFAIL("Exception expected, but not thrown"); } catch (const MyMoneyException &) { // Exception caught as expected. } QVERIFY(m->scheduleList().count() == 1); } void MyMoneyDatabaseMgrTest::testSchedule() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddSchedule(); MyMoneySchedule sched; sched = m->schedule("SCH000001"); QVERIFY(sched.name() == "Sched-Name"); QVERIFY(sched.id() == "SCH000001"); try { m->schedule("SCH000002"); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } void MyMoneyDatabaseMgrTest::testModifySchedule() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddSchedule(); MyMoneySchedule sched; sched = m->schedule("SCH000001"); - sched.setId("SCH000002"); + sched.d_func()->setId("SCH000002"); try { m->modifySchedule(sched); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } sched = m->schedule("SCH000001"); sched.setName("New Sched-Name"); try { m->modifySchedule(sched); QVERIFY(m->scheduleList().count() == 1); QVERIFY((*(m->scheduleList().begin())).name() == "New Sched-Name"); QVERIFY((*(m->scheduleList().begin())).id() == "SCH000001"); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyDatabaseMgrTest::testRemoveSchedule() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddSchedule(); MyMoneySchedule sched; sched = m->schedule("SCH000001"); - sched.setId("SCH000002"); + sched.d_func()->setId("SCH000002"); try { m->removeSchedule(sched); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } sched = m->schedule("SCH000001"); try { m->removeSchedule(sched); QVERIFY(m->scheduleList().count() == 0); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyDatabaseMgrTest::testScheduleList() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); // put some accounts in the db, so the tests don't break testReparentAccount(); QDate testDate = QDate::currentDate(); QDate notOverdue = testDate.addDays(2); QDate overdue = testDate.addDays(-2); MyMoneyTransaction t1; MyMoneySplit s1, s2; s1.setAccountId("A000001"); t1.addSplit(s1); s2.setAccountId("A000002"); t1.addSplit(s2); MyMoneySchedule schedule1("Schedule 1", Schedule::Type::Bill, Schedule::Occurrence::Once, 1, Schedule::PaymentType::DirectDebit, QDate(), QDate(), false, false); t1.setPostDate(notOverdue); schedule1.setTransaction(t1); schedule1.setLastPayment(notOverdue); MyMoneyTransaction t2; MyMoneySplit s3, s4; s3.setAccountId("A000001"); t2.addSplit(s3); s4.setAccountId("A000003"); t2.addSplit(s4); MyMoneySchedule schedule2("Schedule 2", Schedule::Type::Deposit, Schedule::Occurrence::Daily, 1, Schedule::PaymentType::DirectDeposit, QDate(), QDate(), false, false); t2.setPostDate(notOverdue.addDays(1)); schedule2.setTransaction(t2); schedule2.setLastPayment(notOverdue.addDays(1)); MyMoneyTransaction t3; MyMoneySplit s5, s6; s5.setAccountId("A000005"); t3.addSplit(s5); s6.setAccountId("A000006"); t3.addSplit(s6); MyMoneySchedule schedule3("Schedule 3", Schedule::Type::Transfer, Schedule::Occurrence::Weekly, 1, Schedule::PaymentType::Other, QDate(), QDate(), false, false); t3.setPostDate(notOverdue.addDays(2)); schedule3.setTransaction(t3); schedule3.setLastPayment(notOverdue.addDays(2)); MyMoneyTransaction t4; MyMoneySplit s7, s8; s7.setAccountId("A000005"); t4.addSplit(s7); s8.setAccountId("A000006"); t4.addSplit(s8); MyMoneySchedule schedule4("Schedule 4", Schedule::Type::Bill, Schedule::Occurrence::Weekly, 1, Schedule::PaymentType::WriteChecque, QDate(), notOverdue.addDays(31), false, false); t4.setPostDate(overdue.addDays(-7)); schedule4.setTransaction(t4); try { m->addSchedule(schedule1); m->addSchedule(schedule2); m->addSchedule(schedule3); m->addSchedule(schedule4); } catch (const MyMoneyException &e) { unexpectedException(e); } QList list; // no filter list = m->scheduleList(); QVERIFY(list.count() == 4); // filter by type list = m->scheduleList(QString(), Schedule::Type::Bill); QVERIFY(list.count() == 2); QVERIFY(list[0].name() == "Schedule 1"); QVERIFY(list[1].name() == "Schedule 4"); // filter by occurrence list = m->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Daily); QVERIFY(list.count() == 1); QVERIFY(list[0].name() == "Schedule 2"); // filter by payment type list = m->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::DirectDeposit); QVERIFY(list.count() == 1); QVERIFY(list[0].name() == "Schedule 2"); // filter by account list = m->scheduleList("A01"); QVERIFY(list.count() == 0); list = m->scheduleList("A000001"); QVERIFY(list.count() == 2); list = m->scheduleList("A000002"); QVERIFY(list.count() == 1); // filter by start date list = m->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, notOverdue.addDays(31), QDate(), false); QVERIFY(list.count() == 3); QVERIFY(list[0].name() == "Schedule 2"); QVERIFY(list[1].name() == "Schedule 3"); QVERIFY(list[2].name() == "Schedule 4"); // filter by end date list = m->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), notOverdue.addDays(1), false); QVERIFY(list.count() == 3); QVERIFY(list[0].name() == "Schedule 1"); QVERIFY(list[1].name() == "Schedule 2"); QVERIFY(list[2].name() == "Schedule 4"); // filter by start and end date list = m->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, notOverdue.addDays(-1), notOverdue.addDays(1), false); QVERIFY(list.count() == 2); QVERIFY(list[0].name() == "Schedule 1"); QVERIFY(list[1].name() == "Schedule 2"); // filter by overdue status list = m->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), true); QVERIFY(list.count() == 1); QVERIFY(list[0].name() == "Schedule 4"); } void MyMoneyDatabaseMgrTest::testAddCurrency() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneySecurity curr("EUR", "Euro", "?", 100, 100); QVERIFY(m->currencyList().count() == 0); m->setDirty(); try { m->addCurrency(curr); QVERIFY(m->currencyList().count() == 1); QVERIFY((*(m->currencyList().begin())).name() == "Euro"); QVERIFY((*(m->currencyList().begin())).id() == "EUR"); } catch (const MyMoneyException &e) { unexpectedException(e); } m->setDirty(); try { m->addCurrency(curr); QFAIL("Expected exception missing"); } catch (const MyMoneyException &) { QVERIFY(m->dirty() == false); } } void MyMoneyDatabaseMgrTest::testModifyCurrency() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneySecurity curr("EUR", "Euro", "?", 100, 100); testAddCurrency(); m->setDirty(); curr.setName("EURO"); try { m->modifyCurrency(curr); QVERIFY(m->currencyList().count() == 1); QVERIFY((*(m->currencyList().begin())).name() == "EURO"); QVERIFY((*(m->currencyList().begin())).id() == "EUR"); } catch (const MyMoneyException &e) { unexpectedException(e); } m->setDirty(); MyMoneySecurity unknownCurr("DEM", "Deutsche Mark", "DM", 100, 100); try { m->modifyCurrency(unknownCurr); QFAIL("Expected exception missing"); } catch (const MyMoneyException &) { QVERIFY(m->dirty() == false); } } void MyMoneyDatabaseMgrTest::testRemoveCurrency() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneySecurity curr("EUR", "Euro", "?", 100, 100); testAddCurrency(); m->setDirty(); try { m->removeCurrency(curr); QVERIFY(m->currencyList().count() == 0); } catch (const MyMoneyException &e) { unexpectedException(e); } m->setDirty(); MyMoneySecurity unknownCurr("DEM", "Deutsche Mark", "DM", 100, 100); try { m->removeCurrency(unknownCurr); QFAIL("Expected exception missing"); } catch (const MyMoneyException &) { QVERIFY(m->dirty() == false); } } void MyMoneyDatabaseMgrTest::testCurrency() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneySecurity curr("EUR", "Euro", "?", 100, 100); MyMoneySecurity newCurr; testAddCurrency(); m->setDirty(); try { newCurr = m->currency("EUR"); QVERIFY(m->dirty() == false); QVERIFY(newCurr.id() == curr.id()); QVERIFY(newCurr.name() == curr.name()); } catch (const MyMoneyException &e) { unexpectedException(e); } try { m->currency("DEM"); QFAIL("Expected exception missing"); } catch (const MyMoneyException &) { QVERIFY(m->dirty() == false); } } void MyMoneyDatabaseMgrTest::testCurrencyList() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); QVERIFY(m->currencyList().count() == 0); testAddCurrency(); QVERIFY(m->currencyList().count() == 1); MyMoneySecurity unknownCurr("DEM", "Deutsche Mark", "DM", 100, 100); try { m->addCurrency(unknownCurr); m->setDirty(); QVERIFY(m->currencyList().count() == 2); QVERIFY(m->currencyList().count() == 2); QVERIFY(m->dirty() == false); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyDatabaseMgrTest::testAccountList() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); QList accounts; m->accountList(accounts); QVERIFY(accounts.count() == 0); testAddNewAccount(); accounts.clear(); m->accountList(accounts); QVERIFY(accounts.count() == 2); MyMoneyAccount a = m->account("A000001"); MyMoneyAccount b = m->account("A000002"); m->reparentAccount(b, a); accounts.clear(); m->accountList(accounts); QVERIFY(accounts.count() == 2); } void MyMoneyDatabaseMgrTest::testAddOnlineJob() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); // Add a onlineJob onlineJob job(new dummyTask()); QCOMPARE(m->onlineJobList().count(), 0); m->setDirty(); QSKIP("Test not fully implemented, yet.", SkipAll); try { m->addOnlineJob(job); QCOMPARE(m->onlineJobList().count(), 1); QCOMPARE((*(m->onlineJobList().begin())).id(), QLatin1String("O00000001")); } catch (const MyMoneyException &e) { unexpectedException(e); } // Try to re-add the same job. It should fail. m->setDirty(); try { m->addOnlineJob(job); QFAIL("Expected exception missing"); } catch (const MyMoneyException &) { QCOMPARE(m->dirty(), false); } } void MyMoneyDatabaseMgrTest::testModifyOnlineJob() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); onlineJob job(new dummyTask()); testAddOnlineJob(); m->setDirty(); QSKIP("Test not fully implemented, yet.", SkipAll); // update online job try { m->modifyOnlineJob(job); QVERIFY(m->onlineJobList().count() == 1); //QVERIFY((*(m->onlineJobList().begin())).name() == "EURO"); QVERIFY((*(m->onlineJobList().begin())).id() == "O00000001"); } catch (const MyMoneyException &e) { unexpectedException(e); } m->setDirty(); onlineJob unknownJob(new dummyTask()); try { m->modifyOnlineJob(unknownJob); QFAIL("Expected exception missing"); } catch (const MyMoneyException &) { QVERIFY(m->dirty() == false); } } void MyMoneyDatabaseMgrTest::testRemoveOnlineJob() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); onlineJob job(new dummyTask()); testAddOnlineJob(); m->setDirty(); QSKIP("Test not fully implemented, yet.", SkipAll); try { m->removeOnlineJob(job); QVERIFY(m->onlineJobList().count() == 0); } catch (const MyMoneyException &e) { unexpectedException(e); } m->setDirty(); onlineJob unknownJob(new dummyTask()); try { m->removeOnlineJob(unknownJob); QFAIL("Expected exception missing"); } catch (const MyMoneyException &) { QVERIFY(m->dirty() == false); } } void MyMoneyDatabaseMgrTest::testHighestNumberFromIdString() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddTransactions(); QCOMPARE(m->m_sql->highestNumberFromIdString(QLatin1String("kmmTransactions"), QLatin1String("id"), 1), 2ul); QCOMPARE(m->m_sql->highestNumberFromIdString(QLatin1String("kmmAccounts"), QLatin1String("id"), 1), 6ul); } diff --git a/kmymoney/mymoney/storage/tests/mymoneyseqaccessmgr-test.cpp b/kmymoney/mymoney/storage/tests/mymoneyseqaccessmgr-test.cpp index 88fde8230..b32ecdbe4 100644 --- a/kmymoney/mymoney/storage/tests/mymoneyseqaccessmgr-test.cpp +++ b/kmymoney/mymoney/storage/tests/mymoneyseqaccessmgr-test.cpp @@ -1,1820 +1,1822 @@ /*************************************************************************** mymoneyseqaccessmgrtest.cpp ------------------- copyright : (C) 2002 by Thomas Baumgart email : ipwizard@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneyseqaccessmgr-test.h" #include #include #include #include "mymoneytestutils.h" #include "mymoneymoney.h" #include "mymoneyfile.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneytag.h" #include "mymoneypayee.h" #include "mymoneyschedule.h" +#include "mymoneyschedule_p.h" #include "mymoneyreport.h" #include "mymoneysplit.h" +#include "mymoneysplit_p.h" #include "mymoneytransaction.h" #include "mymoneybudget.h" #include "mymoneyprice.h" #include "onlinetasks/dummy/tasks/dummytask.h" #include "mymoneyenums.h" using namespace eMyMoney; QTEST_GUILESS_MAIN(MyMoneySeqAccessMgrTest) void MyMoneySeqAccessMgrTest::init() { m = new MyMoneySeqAccessMgr; MyMoneyFile* file = MyMoneyFile::instance(); file->attachStorage(m); m->startTransaction(); } void MyMoneySeqAccessMgrTest::cleanup() { m->commitTransaction(); MyMoneyFile* file = MyMoneyFile::instance(); file->detachStorage(m); delete m; } void MyMoneySeqAccessMgrTest::testEmptyConstructor() { MyMoneyPayee user = m->user(); QVERIFY(user.name().isEmpty()); QVERIFY(user.address().isEmpty()); QVERIFY(user.city().isEmpty()); QVERIFY(user.state().isEmpty()); QVERIFY(user.postcode().isEmpty()); QVERIFY(user.telephone().isEmpty()); QVERIFY(user.email().isEmpty()); QCOMPARE(m->m_nextInstitutionID, 0ul); QCOMPARE(m->m_nextAccountID, 0ul); QCOMPARE(m->m_nextTransactionID, 0ul); QCOMPARE(m->m_nextPayeeID, 0ul); QCOMPARE(m->m_nextScheduleID, 0ul); QCOMPARE(m->m_nextReportID, 0ul); QCOMPARE(m->m_institutionList.count(), 0); QCOMPARE(m->m_accountList.count(), 5); QCOMPARE(m->m_transactionList.count(), 0); QCOMPARE(m->m_transactionKeys.count(), 0); QCOMPARE(m->m_payeeList.count(), 0); QCOMPARE(m->m_tagList.count(), 0); QCOMPARE(m->m_scheduleList.count(), 0); QCOMPARE(m->m_dirty, false); QCOMPARE(m->m_creationDate, QDate::currentDate()); QCOMPARE(m->liability().name(), QLatin1String("Liability")); QCOMPARE(m->asset().name(), QLatin1String("Asset")); QCOMPARE(m->expense().name(), QLatin1String("Expense")); QCOMPARE(m->income().name(), QLatin1String("Income")); QCOMPARE(m->equity().name(), QLatin1String("Equity")); } void MyMoneySeqAccessMgrTest::testSetFunctions() { MyMoneyPayee user = m->user(); m->m_dirty = false; user.setName("Name"); m->setUser(user); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); m->m_dirty = false; user.setAddress("Street"); m->setUser(user); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); m->m_dirty = false; user.setCity("Town"); m->setUser(user); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); m->m_dirty = false; user.setState("County"); m->setUser(user); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); m->m_dirty = false; user.setPostcode("Postcode"); m->setUser(user); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); m->m_dirty = false; user.setTelephone("Telephone"); m->setUser(user); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); m->m_dirty = false; user.setEmail("Email"); m->setUser(user); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); m->m_dirty = false; m->setValue("key", "value"); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); 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")); QCOMPARE(m->value("key"), QLatin1String("value")); m->m_dirty = false; m->deletePair("key"); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); } void MyMoneySeqAccessMgrTest::testSupportFunctions() { QCOMPARE(m->nextInstitutionID(), QLatin1String("I000001")); QCOMPARE(m->m_nextInstitutionID, 1ul); QCOMPARE(m->nextAccountID(), QLatin1String("A000001")); QCOMPARE(m->m_nextAccountID, 1ul); QCOMPARE(m->nextTransactionID(), QLatin1String("T000000000000000001")); QCOMPARE(m->m_nextTransactionID, 1ul); QCOMPARE(m->nextPayeeID(), QLatin1String("P000001")); QCOMPARE(m->m_nextPayeeID, 1ul); QCOMPARE(m->nextTagID(), QLatin1String("G000001")); QCOMPARE(m->m_nextTagID, 1ul); QCOMPARE(m->nextScheduleID(), QLatin1String("SCH000001")); QCOMPARE(m->m_nextScheduleID, 1ul); QCOMPARE(m->nextReportID(), QLatin1String("R000001")); QCOMPARE(m->m_nextReportID, 1ul); QCOMPARE(m->nextOnlineJobID(), QLatin1String("O000001")); QCOMPARE(m->m_nextOnlineJobID, 1ul); } void MyMoneySeqAccessMgrTest::testIsStandardAccount() { QCOMPARE(m->isStandardAccount(STD_ACC_LIABILITY), true); QCOMPARE(m->isStandardAccount(STD_ACC_ASSET), true); QCOMPARE(m->isStandardAccount(STD_ACC_EXPENSE), true); QCOMPARE(m->isStandardAccount(STD_ACC_INCOME), true); QCOMPARE(m->isStandardAccount("A0002"), false); } void MyMoneySeqAccessMgrTest::testNewAccount() { MyMoneyAccount a; a.setName("AccountName"); a.setNumber("AccountNumber"); m->addAccount(a); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->m_nextAccountID, 1ul); QCOMPARE(m->dirty(), true); QCOMPARE(m->m_accountList.count(), 6); QCOMPARE(m->m_accountList["A000001"].name(), QLatin1String("AccountName")); } void MyMoneySeqAccessMgrTest::testAccount() { testNewAccount(); m->m_dirty = false; MyMoneyAccount a; // make sure that an invalid ID causes an exception try { a = m->account("Unknown ID"); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), false); // now make sure, that a real ID works try { a = m->account("A000001"); m->commitTransaction(); m->startTransaction(); QCOMPARE(a.name(), QLatin1String("AccountName")); QCOMPARE(a.id(), QLatin1String("A000001")); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneySeqAccessMgrTest::testAddNewAccount() { testNewAccount(); MyMoneyAccount a, b; b.setName("Account2"); b.setNumber("Acc2"); m->addAccount(b); m->commitTransaction(); m->startTransaction(); m->m_dirty = false; QCOMPARE(m->m_nextAccountID, 2ul); QCOMPARE(m->m_accountList.count(), 7); // try to add account to undefined account try { MyMoneyAccount c("UnknownID", b); m->addAccount(c, a); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), false); // now try to add account 1 as sub-account to account 2 a = m->account("A000001"); try { QCOMPARE(m->m_accountList[STD_ACC_ASSET].accountList().count(), 0); m->addAccount(b, a); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->m_accountList["A000002"].accountList()[0], QLatin1String("A000001")); QCOMPARE(m->m_accountList["A000002"].accountList().count(), 1); QCOMPARE(m->m_accountList[STD_ACC_ASSET].accountList().count(), 0); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneySeqAccessMgrTest::testAddInstitution() { MyMoneyInstitution i; i.setName("Inst Name"); m->addInstitution(i); QCOMPARE(m->m_institutionList.count(), 1); QCOMPARE(m->m_nextInstitutionID, 1ul); QCOMPARE(m->m_institutionList["I000001"].name(), QLatin1String("Inst Name")); } void MyMoneySeqAccessMgrTest::testInstitution() { testAddInstitution(); MyMoneyInstitution i; m->m_dirty = false; // try to find unknown institution try { i = m->institution("Unknown ID"); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } QCOMPARE(m->dirty(), false); // now try to find real institution try { i = m->institution("I000001"); QCOMPARE(i.name(), QLatin1String("Inst Name")); QCOMPARE(m->dirty(), false); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneySeqAccessMgrTest::testAccount2Institution() { testAddInstitution(); testAddNewAccount(); MyMoneyInstitution i; MyMoneyAccount a, b; try { i = m->institution("I000001"); a = m->account("A000001"); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } m->m_dirty = false; // try to add to a false institution MyMoneyInstitution fake("Unknown ID", i); a.setInstitutionId(fake.id()); try { m->modifyAccount(a); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), false); // now try to do it with a real institution try { QCOMPARE(i.accountList().count(), 0); a.setInstitutionId(i.id()); m->modifyAccount(a); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); QCOMPARE(a.institutionId(), i.id()); b = m->account("A000001"); QCOMPARE(b.institutionId(), i.id()); QCOMPARE(i.accountList().count(), 0); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneySeqAccessMgrTest::testModifyAccount() { testAccount2Institution(); // test the OK case MyMoneyAccount a = m->account("A000001"); a.setName("New account name"); m->m_dirty = false; try { m->modifyAccount(a); m->commitTransaction(); m->startTransaction(); MyMoneyAccount b = m->account("A000001"); QCOMPARE(b.parentAccountId(), a.parentAccountId()); QCOMPARE(b.name(), QLatin1String("New account name")); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } // modify institution to unknown id MyMoneyAccount c("Unknown ID", a); m->m_dirty = false; try { m->modifyAccount(c); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } // use different account type MyMoneyAccount d; d.setAccountType(Account::Type::CreditCard); MyMoneyAccount f("A000001", d); try { m->modifyAccount(f); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } // use different parent a.setParentAccountId("A000002"); try { m->modifyAccount(c); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } void MyMoneySeqAccessMgrTest::testModifyInstitution() { testAddInstitution(); MyMoneyInstitution i = m->institution("I000001"); m->m_dirty = false; i.setName("New inst name"); try { m->modifyInstitution(i); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); i = m->institution("I000001"); QCOMPARE(i.name(), QLatin1String("New inst name")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } // try to modify an institution that does not exist MyMoneyInstitution f("Unknown ID", i); try { m->modifyInstitution(f); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } void MyMoneySeqAccessMgrTest::testReparentAccount() { // this one adds some accounts to the database MyMoneyAccount ex1; ex1.setAccountType(Account::Type::Expense); MyMoneyAccount ex2; ex2.setAccountType(Account::Type::Expense); MyMoneyAccount ex3; ex3.setAccountType(Account::Type::Expense); MyMoneyAccount ex4; ex4.setAccountType(Account::Type::Expense); MyMoneyAccount in; in.setAccountType(Account::Type::Income); MyMoneyAccount ch; ch.setAccountType(Account::Type::Checkings); ex1.setName("Sales Tax"); ex2.setName("Sales Tax 16%"); ex3.setName("Sales Tax 7%"); ex4.setName("Grosseries"); in.setName("Salary"); ch.setName("My checkings account"); try { m->addAccount(ex1); m->addAccount(ex2); m->addAccount(ex3); m->addAccount(ex4); m->addAccount(in); m->addAccount(ch); QCOMPARE(ex1.id(), QLatin1String("A000001")); QCOMPARE(ex2.id(), QLatin1String("A000002")); QCOMPARE(ex3.id(), QLatin1String("A000003")); QCOMPARE(ex4.id(), QLatin1String("A000004")); QCOMPARE(in.id(), QLatin1String("A000005")); QCOMPARE(ch.id(), QLatin1String("A000006")); MyMoneyAccount parent = m->expense(); m->addAccount(parent, ex1); m->addAccount(ex1, ex2); m->addAccount(parent, ex3); m->addAccount(parent, ex4); parent = m->income(); m->addAccount(parent, in); parent = m->asset(); m->addAccount(parent, ch); QCOMPARE(m->expense().accountCount(), 3); QCOMPARE(m->account(ex1.id()).accountCount(), 1); QCOMPARE(ex3.parentAccountId(), QLatin1String(STD_ACC_EXPENSE)); m->reparentAccount(ex3, ex1); QCOMPARE(m->expense().accountCount(), 2); QCOMPARE(m->account(ex1.id()).accountCount(), 2); QCOMPARE(ex3.parentAccountId(), ex1.id()); } catch (const MyMoneyException &e) { std::cout << std::endl << qPrintable(e.what()) << std::endl; QFAIL("Unexpected exception"); } } void MyMoneySeqAccessMgrTest::testAddTransactions() { testReparentAccount(); MyMoneyAccount ch; MyMoneyTransaction t1, t2; MyMoneySplit s; try { // I made some money, great s.setAccountId("A000006"); // Checkings s.setShares(MyMoneyMoney(100000, 100)); s.setValue(MyMoneyMoney(100000, 100)); QVERIFY(s.id().isEmpty()); t1.addSplit(s); - s.setId(QString()); // enable re-usage of split variable + s.d_func()->setId(QString()); // enable re-usage of split variable s.setAccountId("A000005"); // Salary s.setShares(MyMoneyMoney(-100000, 100)); s.setValue(MyMoneyMoney(-100000, 100)); QVERIFY(s.id().isEmpty()); t1.addSplit(s); t1.setPostDate(QDate(2002, 5, 10)); } catch (const MyMoneyException &e) { unexpectedException(e); } m->m_dirty = false; try { m->addTransaction(t1); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); QCOMPARE(t1.id(), QLatin1String("T000000000000000001")); QCOMPARE(t1.splitCount(), 2u); QCOMPARE(m->transactionCount(), 1u); } catch (const MyMoneyException &e) { unexpectedException(e); } try { // I spent some money, not so great - s.setId(QString()); // enable re-usage of split variable + s.d_func()->setId(QString()); // enable re-usage of split variable s.setAccountId("A000004"); // Grosseries s.setShares(MyMoneyMoney(10000, 100)); s.setValue(MyMoneyMoney(10000, 100)); QVERIFY(s.id().isEmpty()); t2.addSplit(s); - s.setId(QString()); // enable re-usage of split variable + s.d_func()->setId(QString()); // enable re-usage of split variable s.setAccountId("A000002"); // 16% sales tax s.setShares(MyMoneyMoney(1200, 100)); s.setValue(MyMoneyMoney(1200, 100)); QVERIFY(s.id().isEmpty()); t2.addSplit(s); - s.setId(QString()); // enable re-usage of split variable + s.d_func()->setId(QString()); // enable re-usage of split variable s.setAccountId("A000003"); // 7% sales tax s.setShares(MyMoneyMoney(400, 100)); s.setValue(MyMoneyMoney(400, 100)); QVERIFY(s.id().isEmpty()); t2.addSplit(s); - s.setId(QString()); // enable re-usage of split variable + s.d_func()->setId(QString()); // enable re-usage of split variable s.setAccountId("A000006"); // Checkings account s.setShares(MyMoneyMoney(-11600, 100)); s.setValue(MyMoneyMoney(-11600, 100)); QVERIFY(s.id().isEmpty()); t2.addSplit(s); t2.setPostDate(QDate(2002, 5, 9)); } catch (const MyMoneyException &e) { unexpectedException(e); } m->m_dirty = false; try { m->addTransaction(t2); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); QCOMPARE(t2.id(), QLatin1String("T000000000000000002")); QCOMPARE(t2.splitCount(), 4u); QCOMPARE(m->transactionCount(), 2u); QMap::ConstIterator it_k; QMap::ConstIterator it_t; it_k = m->m_transactionKeys.begin(); it_t = m->m_transactionList.begin(); QCOMPARE((*it_k), QLatin1String("2002-05-10-T000000000000000001")); QCOMPARE((*it_t).id(), QLatin1String("T000000000000000002")); ++it_k; ++it_t; QCOMPARE((*it_k), QLatin1String("2002-05-09-T000000000000000002")); QCOMPARE((*it_t).id(), QLatin1String("T000000000000000001")); ++it_k; ++it_t; QCOMPARE(it_k, m->m_transactionKeys.end()); QCOMPARE(it_t, m->m_transactionList.end()); ch = m->account("A000006"); // check that the account's transaction list is updated QList list; MyMoneyTransactionFilter filter("A000006"); list = m->transactionList(filter); QCOMPARE(list.size(), 2); QList::ConstIterator it; it = list.constBegin(); QCOMPARE((*it).id(), QLatin1String("T000000000000000002")); ++it; QCOMPARE((*it).id(), QLatin1String("T000000000000000001")); ++it; QCOMPARE(it, list.constEnd()); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneySeqAccessMgrTest::testTransactionCount() { testAddTransactions(); QCOMPARE(m->transactionCount("A000001"), 0u); QCOMPARE(m->transactionCount("A000002"), 1u); QCOMPARE(m->transactionCount("A000003"), 1u); QCOMPARE(m->transactionCount("A000004"), 1u); QCOMPARE(m->transactionCount("A000005"), 1u); QCOMPARE(m->transactionCount("A000006"), 2u); } void MyMoneySeqAccessMgrTest::testBalance() { testAddTransactions(); QVERIFY(m->balance("A000001").isZero()); QCOMPARE(m->balance("A000002"), MyMoneyMoney(1200, 100)); QCOMPARE(m->balance("A000003"), MyMoneyMoney(400, 100)); QCOMPARE(m->totalBalance("A000001"), MyMoneyMoney(1600, 100)); QCOMPARE(m->balance("A000006", QDate(2002, 5, 9)), MyMoneyMoney(-11600, 100)); QCOMPARE(m->balance("A000005", QDate(2002, 5, 10)), MyMoneyMoney(-100000, 100)); QCOMPARE(m->balance("A000006", QDate(2002, 5, 10)), MyMoneyMoney(88400, 100)); } void MyMoneySeqAccessMgrTest::testModifyTransaction() { testAddTransactions(); MyMoneyTransaction t = m->transaction("T000000000000000002"); MyMoneySplit s; MyMoneyAccount ch; // just modify simple stuff (splits) QCOMPARE(t.splitCount(), 4u); s = t.splits()[0]; s.setShares(MyMoneyMoney(11000, 100)); s.setValue(MyMoneyMoney(11000, 100)); t.modifySplit(s); QCOMPARE(t.splitCount(), 4u); s = t.splits()[3]; s.setShares(MyMoneyMoney(-12600, 100)); s.setValue(MyMoneyMoney(-12600, 100)); t.modifySplit(s); try { QCOMPARE(m->balance("A000004"), MyMoneyMoney(10000, 100)); QCOMPARE(m->balance("A000006"), MyMoneyMoney(100000 - 11600, 100)); QCOMPARE(m->totalBalance("A000001"), MyMoneyMoney(1600, 100)); m->modifyTransaction(t); QCOMPARE(m->balance("A000004"), MyMoneyMoney(11000, 100)); QCOMPARE(m->balance("A000006"), MyMoneyMoney(100000 - 12600, 100)); QCOMPARE(m->totalBalance("A000001"), MyMoneyMoney(1600, 100)); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } // now modify the date t.setPostDate(QDate(2002, 5, 11)); try { m->modifyTransaction(t); QCOMPARE(m->balance("A000004"), MyMoneyMoney(11000, 100)); QCOMPARE(m->balance("A000006"), MyMoneyMoney(100000 - 12600, 100)); QCOMPARE(m->totalBalance("A000001"), MyMoneyMoney(1600, 100)); QMap::ConstIterator it_k; QMap::ConstIterator it_t; it_k = m->m_transactionKeys.begin(); it_t = m->m_transactionList.begin(); QCOMPARE((*it_k), QLatin1String("2002-05-10-T000000000000000001")); QCOMPARE((*it_t).id(), QLatin1String("T000000000000000001")); ++it_k; ++it_t; QCOMPARE((*it_k), QLatin1String("2002-05-11-T000000000000000002")); QCOMPARE((*it_t).id(), QLatin1String("T000000000000000002")); ++it_k; ++it_t; QCOMPARE(it_k, m->m_transactionKeys.end()); QCOMPARE(it_t, m->m_transactionList.end()); ch = m->account("A000006"); // check that the account's transaction list is updated QList list; MyMoneyTransactionFilter filter("A000006"); list = m->transactionList(filter); QCOMPARE(list.size(), 2); QList::ConstIterator it; it = list.constBegin(); QCOMPARE((*it).id(), QLatin1String("T000000000000000001")); ++it; QCOMPARE((*it).id(), QLatin1String("T000000000000000002")); ++it; QCOMPARE(it, list.constEnd()); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneySeqAccessMgrTest::testRemoveUnusedAccount() { testAccount2Institution(); MyMoneyAccount a = m->account("A000001"); MyMoneyInstitution i = m->institution("I000001"); m->m_dirty = false; // make sure, we cannot remove the standard account groups try { m->removeAccount(m->liability()); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } try { m->removeAccount(m->asset()); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } try { m->removeAccount(m->expense()); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } try { m->removeAccount(m->income()); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } // try to remove the account still attached to the institution try { m->removeAccount(a); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } // now really remove an account try { QCOMPARE(i.accountCount(), 0u); QCOMPARE(m->accountCount(), 7u); a.setInstitutionId(QString()); m->modifyAccount(a); m->removeAccount(a); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->accountCount(), 6u); QCOMPARE(m->dirty(), true); i = m->institution("I000001"); QCOMPARE(i.accountCount(), 0u); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneySeqAccessMgrTest::testRemoveUsedAccount() { testAddTransactions(); MyMoneyAccount a = m->account("A000006"); try { m->removeAccount(a); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } void MyMoneySeqAccessMgrTest::testRemoveInstitution() { testModifyInstitution(); testReparentAccount(); MyMoneyInstitution i; MyMoneyAccount a; // assign the checkings account to the institution try { i = m->institution("I000001"); a = m->account("A000006"); a.setInstitutionId(i.id()); m->modifyAccount(a); QCOMPARE(i.accountCount(), 0u); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } m->m_dirty = false; // now remove the institution and see if the account survived ;-) try { m->removeInstitution(i); a.setInstitutionId(QString()); m->modifyAccount(a); m->commitTransaction(); m->startTransaction(); a = m->account("A000006"); QCOMPARE(m->dirty(), true); QVERIFY(a.institutionId().isEmpty()); QCOMPARE(m->institutionCount(), 0u); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneySeqAccessMgrTest::testRemoveTransaction() { testAddTransactions(); MyMoneyTransaction t = m->transaction("T000000000000000002"); m->m_dirty = false; try { m->removeTransaction(t); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); QCOMPARE(m->transactionCount(), 1u); /* removed with MyMoneyAccount::Transaction QCOMPARE(m->account("A000006").transactionCount(), 1); */ } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneySeqAccessMgrTest::testTransactionList() { testAddTransactions(); QList list; MyMoneyTransactionFilter filter("A000006"); list = m->transactionList(filter); QCOMPARE(list.count(), 2); QCOMPARE(list.at(0).id(), QLatin1String("T000000000000000002")); QCOMPARE(list.at(1).id(), QLatin1String("T000000000000000001")); filter.clear(); filter.addAccount(QString("A000003")); list = m->transactionList(filter); QCOMPARE(list.count(), 1); QCOMPARE(list.at(0).id(), QLatin1String("T000000000000000002")); filter.clear(); list = m->transactionList(filter); QCOMPARE(list.count(), 2); QCOMPARE(list.at(0).id(), QLatin1String("T000000000000000002")); QCOMPARE(list.at(1).id(), QLatin1String("T000000000000000001")); } void MyMoneySeqAccessMgrTest::testAddPayee() { MyMoneyPayee p; p.setName("THB"); m->m_dirty = false; try { QCOMPARE(m->m_nextPayeeID, 0ul); m->addPayee(p); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); QCOMPARE(m->m_nextPayeeID, 1ul); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneySeqAccessMgrTest::testSetAccountName() { try { m->setAccountName(STD_ACC_LIABILITY, "Verbindlichkeiten"); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } try { m->setAccountName(STD_ACC_ASSET, QString("Vermögen")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } try { m->setAccountName(STD_ACC_EXPENSE, "Ausgaben"); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } try { m->setAccountName(STD_ACC_INCOME, "Einnahmen"); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } QCOMPARE(m->liability().name(), QLatin1String("Verbindlichkeiten")); QCOMPARE(m->asset().name(), QString("Vermögen")); QCOMPARE(m->expense().name(), QLatin1String("Ausgaben")); QCOMPARE(m->income().name(), QLatin1String("Einnahmen")); try { m->setAccountName("A000001", "New account name"); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } void MyMoneySeqAccessMgrTest::testModifyPayee() { MyMoneyPayee p; testAddPayee(); p = m->payee("P000001"); p.setName("New name"); m->m_dirty = false; try { m->modifyPayee(p); m->commitTransaction(); m->startTransaction(); p = m->payee("P000001"); QCOMPARE(p.name(), QLatin1String("New name")); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneySeqAccessMgrTest::testRemovePayee() { testAddPayee(); m->m_dirty = false; // check that we can remove an unreferenced payee MyMoneyPayee p = m->payee("P000001"); try { QCOMPARE(m->m_payeeList.count(), 1); m->removePayee(p); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->m_payeeList.count(), 0); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } // add transaction testAddTransactions(); MyMoneyTransaction tr = m->transaction("T000000000000000001"); MyMoneySplit sp; sp = tr.splits()[0]; sp.setPayeeId("P000001"); tr.modifySplit(sp); // check that we cannot add a transaction referencing // an unknown payee try { m->modifyTransaction(tr); QFAIL("Expected exception"); } catch (const MyMoneyException &) { } m->m_nextPayeeID = 0; // reset here, so that the // testAddPayee will not fail testAddPayee(); // check that it works when the payee exists try { m->modifyTransaction(tr); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } m->m_dirty = false; // now check, that we cannot remove the payee try { m->removePayee(p); QFAIL("Expected exception"); } catch (const MyMoneyException &) { } QCOMPARE(m->m_payeeList.count(), 1); } void MyMoneySeqAccessMgrTest::testAddTag() { MyMoneyTag ta; ta.setName("THB"); m->m_dirty = false; try { QCOMPARE(m->m_nextTagID, 0ul); m->addTag(ta); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); QCOMPARE(m->m_nextTagID, 1ul); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneySeqAccessMgrTest::testModifyTag() { MyMoneyTag ta; testAddTag(); ta = m->tag("G000001"); ta.setName("New name"); m->m_dirty = false; try { m->modifyTag(ta); m->commitTransaction(); m->startTransaction(); ta = m->tag("G000001"); QCOMPARE(ta.name(), QLatin1String("New name")); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneySeqAccessMgrTest::testRemoveTag() { testAddTag(); m->m_dirty = false; // check that we can remove an unreferenced tag MyMoneyTag ta = m->tag("G000001"); try { QCOMPARE(m->m_tagList.count(), 1); m->removeTag(ta); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->m_tagList.count(), 0); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } // add transaction testAddTransactions(); MyMoneyTransaction tr = m->transaction("T000000000000000001"); MyMoneySplit sp; sp = tr.splits()[0]; QList tagIdList; tagIdList << "G000001"; sp.setTagIdList(tagIdList); tr.modifySplit(sp); // check that we cannot add a transaction referencing // an unknown tag try { m->modifyTransaction(tr); QFAIL("Expected exception"); } catch (const MyMoneyException &) { } m->m_nextTagID = 0; // reset here, so that the // testAddTag will not fail testAddTag(); // check that it works when the tag exists try { m->modifyTransaction(tr); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } m->m_dirty = false; // now check, that we cannot remove the tag try { m->removeTag(ta); QFAIL("Expected exception"); } catch (const MyMoneyException &) { } QCOMPARE(m->m_tagList.count(), 1); } void MyMoneySeqAccessMgrTest::testRemoveAccountFromTree() { MyMoneyAccount a, b, c; a.setName("Acc A"); b.setName("Acc B"); c.setName("Acc C"); // build a tree A -> B -> C, remove B and see if A -> C // remains in the storag manager try { m->addAccount(a); m->addAccount(b); m->addAccount(c); m->reparentAccount(b, a); m->reparentAccount(c, b); QCOMPARE(a.accountList().count(), 1); QCOMPARE(m->account(a.accountList()[0]).name(), QLatin1String("Acc B")); QCOMPARE(b.accountList().count(), 1); QCOMPARE(m->account(b.accountList()[0]).name(), QLatin1String("Acc C")); QCOMPARE(c.accountList().count(), 0); m->removeAccount(b); // reload account info from titutionIDtorage a = m->account(a.id()); c = m->account(c.id()); try { b = m->account(b.id()); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } QCOMPARE(a.accountList().count(), 1); QCOMPARE(m->account(a.accountList()[0]).name(), QLatin1String("Acc C")); QCOMPARE(c.accountList().count(), 0); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneySeqAccessMgrTest::testPayeeName() { testAddPayee(); MyMoneyPayee p; QString name("THB"); // OK case try { p = m->payeeByName(name); QCOMPARE(p.name(), QLatin1String("THB")); QCOMPARE(p.id(), QLatin1String("P000001")); } catch (const MyMoneyException &e) { unexpectedException(e); } // Not OK case name = "Thb"; try { p = m->payeeByName(name); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } void MyMoneySeqAccessMgrTest::testTagName() { testAddTag(); MyMoneyTag ta; QString name("THB"); // OK case try { ta = m->tagByName(name); QCOMPARE(ta.name(), QLatin1String("THB")); QCOMPARE(ta.id(), QLatin1String("G000001")); } catch (const MyMoneyException &e) { unexpectedException(e); } // Not OK case name = "Thb"; try { ta = m->tagByName(name); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } void MyMoneySeqAccessMgrTest::testAssignment() { testAddTransactions(); MyMoneyPayee user; user.setName("Thomas"); m->setUser(user); MyMoneySeqAccessMgr test = *m; testEquality(&test); } void MyMoneySeqAccessMgrTest::testEquality(const MyMoneySeqAccessMgr *t) { QCOMPARE(m->user().name(), t->user().name()); QCOMPARE(m->user().address(), t->user().address()); QCOMPARE(m->user().city(), t->user().city()); QCOMPARE(m->user().state(), t->user().state()); QCOMPARE(m->user().postcode(), t->user().postcode()); QCOMPARE(m->user().telephone(), t->user().telephone()); QCOMPARE(m->user().email(), t->user().email()); QCOMPARE(m->m_nextInstitutionID, t->m_nextInstitutionID); QCOMPARE(m->m_nextAccountID, t->m_nextAccountID); QCOMPARE(m->m_nextTransactionID, t->m_nextTransactionID); QCOMPARE(m->m_nextPayeeID, t->m_nextPayeeID); QCOMPARE(m->m_nextTagID, t->m_nextTagID); QCOMPARE(m->m_nextScheduleID, t->m_nextScheduleID); QCOMPARE(m->m_dirty, t->m_dirty); QCOMPARE(m->m_creationDate, t->m_creationDate); QCOMPARE(m->m_lastModificationDate, t->m_lastModificationDate); /* * make sure, that the keys and values are the same * on the left and the right side */ QCOMPARE(m->m_payeeList.keys(), t->m_payeeList.keys()); QCOMPARE(m->m_payeeList.values(), t->m_payeeList.values()); QCOMPARE(m->m_tagList.keys(), t->m_tagList.keys()); QCOMPARE(m->m_tagList.values(), t->m_tagList.values()); QCOMPARE(m->m_transactionKeys.keys(), t->m_transactionKeys.keys()); QCOMPARE(m->m_transactionKeys.values(), t->m_transactionKeys.values()); QCOMPARE(m->m_institutionList.keys(), t->m_institutionList.keys()); QCOMPARE(m->m_institutionList.values(), t->m_institutionList.values()); QCOMPARE(m->m_accountList.keys(), t->m_accountList.keys()); QCOMPARE(m->m_accountList.values(), t->m_accountList.values()); QCOMPARE(m->m_transactionList.keys(), t->m_transactionList.keys()); QCOMPARE(m->m_transactionList.values(), t->m_transactionList.values()); // QCOMPARE(m->m_scheduleList.keys(), t->m_scheduleList.keys()); // QCOMPARE(m->m_scheduleList.values(), t->m_scheduleList.values()); } void MyMoneySeqAccessMgrTest::testDuplicate() { const MyMoneySeqAccessMgr* t; testModifyTransaction(); t = m->duplicate(); testEquality(t); delete t; } void MyMoneySeqAccessMgrTest::testAddSchedule() { /* Note addSchedule() now calls validate as it should * so we need an account id. Later this will * be checked to make sure its a valid account id. The * tests currently fail because no splits are defined * for the schedules transaction. */ try { QCOMPARE(m->m_scheduleList.count(), 0); MyMoneyTransaction t1; MyMoneySplit s1, s2; s1.setAccountId("A000001"); t1.addSplit(s1); s2.setAccountId("A000002"); t1.addSplit(s2); MyMoneySchedule schedule("Sched-Name", Schedule::Type::Deposit, Schedule::Occurrence::Daily, 1, Schedule::PaymentType::ManualDeposit, QDate(), QDate(), true, false); t1.setPostDate(QDate(2003, 7, 10)); schedule.setTransaction(t1); m->addSchedule(schedule); QCOMPARE(m->m_scheduleList.count(), 1); QCOMPARE(schedule.id(), QLatin1String("SCH000001")); QCOMPARE(m->m_scheduleList["SCH000001"].id(), QLatin1String("SCH000001")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } try { MyMoneySchedule schedule("Sched-Name", Schedule::Type::Deposit, Schedule::Occurrence::Daily, 1, Schedule::PaymentType::ManualDeposit, QDate(), QDate(), true, false); m->addSchedule(schedule); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } void MyMoneySeqAccessMgrTest::testSchedule() { testAddSchedule(); MyMoneySchedule sched; sched = m->schedule("SCH000001"); QCOMPARE(sched.name(), QLatin1String("Sched-Name")); QCOMPARE(sched.id(), QLatin1String("SCH000001")); try { m->schedule("SCH000002"); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } void MyMoneySeqAccessMgrTest::testModifySchedule() { testAddSchedule(); MyMoneySchedule sched; sched = m->schedule("SCH000001"); - sched.setId("SCH000002"); + sched.d_func()->setId("SCH000002"); try { m->modifySchedule(sched); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } sched = m->schedule("SCH000001"); sched.setName("New Sched-Name"); try { m->modifySchedule(sched); QCOMPARE(m->m_scheduleList.count(), 1); QCOMPARE(m->m_scheduleList["SCH000001"].name(), QLatin1String("New Sched-Name")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneySeqAccessMgrTest::testRemoveSchedule() { testAddSchedule(); m->commitTransaction(); m->startTransaction(); MyMoneySchedule sched; sched = m->schedule("SCH000001"); - sched.setId("SCH000002"); + sched.d_func()->setId("SCH000002"); try { m->removeSchedule(sched); m->commitTransaction(); QFAIL("Exception expected"); } catch (const MyMoneyException &) { m->rollbackTransaction(); } m->startTransaction(); sched = m->schedule("SCH000001"); try { m->removeSchedule(sched); m->commitTransaction(); QCOMPARE(m->m_scheduleList.count(), 0); } catch (const MyMoneyException &) { m->rollbackTransaction(); QFAIL("Unexpected exception"); } m->startTransaction(); } void MyMoneySeqAccessMgrTest::testScheduleList() { QDate testDate = QDate::currentDate(); QDate notOverdue = testDate.addDays(2); QDate overdue = testDate.addDays(-2); MyMoneyTransaction t1; MyMoneySplit s1, s2; s1.setAccountId("A000001"); t1.addSplit(s1); s2.setAccountId("A000002"); t1.addSplit(s2); MyMoneySchedule schedule1("Schedule 1", Schedule::Type::Bill, Schedule::Occurrence::Once, 1, Schedule::PaymentType::DirectDebit, QDate(), QDate(), false, false); t1.setPostDate(notOverdue); schedule1.setTransaction(t1); schedule1.setLastPayment(notOverdue); MyMoneyTransaction t2; MyMoneySplit s3, s4; s3.setAccountId("A000001"); t2.addSplit(s3); s4.setAccountId("A000003"); t2.addSplit(s4); MyMoneySchedule schedule2("Schedule 2", Schedule::Type::Deposit, Schedule::Occurrence::Daily, 1, Schedule::PaymentType::DirectDeposit, QDate(), QDate(), false, false); t2.setPostDate(notOverdue.addDays(1)); schedule2.setTransaction(t2); schedule2.setLastPayment(notOverdue.addDays(1)); MyMoneyTransaction t3; MyMoneySplit s5, s6; s5.setAccountId("A000005"); t3.addSplit(s5); s6.setAccountId("A000006"); t3.addSplit(s6); MyMoneySchedule schedule3("Schedule 3", Schedule::Type::Transfer, Schedule::Occurrence::Weekly, 1, Schedule::PaymentType::Other, QDate(), QDate(), false, false); t3.setPostDate(notOverdue.addDays(2)); schedule3.setTransaction(t3); schedule3.setLastPayment(notOverdue.addDays(2)); MyMoneyTransaction t4; MyMoneySplit s7, s8; s7.setAccountId("A000005"); t4.addSplit(s7); s8.setAccountId("A000006"); t4.addSplit(s8); MyMoneySchedule schedule4("Schedule 4", Schedule::Type::Bill, Schedule::Occurrence::Weekly, 1, Schedule::PaymentType::WriteChecque, QDate(), notOverdue.addDays(31), false, false); t4.setPostDate(overdue.addDays(-7)); schedule4.setTransaction(t4); try { m->addSchedule(schedule1); m->addSchedule(schedule2); m->addSchedule(schedule3); m->addSchedule(schedule4); } catch (const MyMoneyException &e) { qDebug("Error: %s", qPrintable(e.what())); QFAIL("Unexpected exception"); } QList list; // no filter list = m->scheduleList(); QCOMPARE(list.count(), 4); // filter by type list = m->scheduleList("", Schedule::Type::Bill); QCOMPARE(list.count(), 2); QCOMPARE(list[0].name(), QLatin1String("Schedule 1")); QCOMPARE(list[1].name(), QLatin1String("Schedule 4")); // filter by occurrence list = m->scheduleList("", Schedule::Type::Any, Schedule::Occurrence::Daily); QCOMPARE(list.count(), 1); QCOMPARE(list[0].name(), QLatin1String("Schedule 2")); // filter by payment type list = m->scheduleList("", Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::DirectDeposit); QCOMPARE(list.count(), 1); QCOMPARE(list[0].name(), QLatin1String("Schedule 2")); // filter by account list = m->scheduleList("A01"); QCOMPARE(list.count(), 0); list = m->scheduleList("A000001"); QCOMPARE(list.count(), 2); list = m->scheduleList("A000002"); QCOMPARE(list.count(), 1); // filter by start date list = m->scheduleList("", Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, notOverdue.addDays(31)); QCOMPARE(list.count(), 3); QCOMPARE(list[0].name(), QLatin1String("Schedule 2")); QCOMPARE(list[1].name(), QLatin1String("Schedule 3")); QCOMPARE(list[2].name(), QLatin1String("Schedule 4")); // filter by end date list = m->scheduleList("", Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), notOverdue.addDays(1)); QCOMPARE(list.count(), 3); QCOMPARE(list[0].name(), QLatin1String("Schedule 1")); QCOMPARE(list[1].name(), QLatin1String("Schedule 2")); QCOMPARE(list[2].name(), QLatin1String("Schedule 4")); // filter by start and end date list = m->scheduleList("", Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, notOverdue.addDays(-1), notOverdue.addDays(1)); QCOMPARE(list.count(), 2); QCOMPARE(list[0].name(), QLatin1String("Schedule 1")); QCOMPARE(list[1].name(), QLatin1String("Schedule 2")); // filter by overdue status list = m->scheduleList("", Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), true); QCOMPARE(list.count(), 1); QCOMPARE(list[0].name(), QLatin1String("Schedule 4")); } void MyMoneySeqAccessMgrTest::testAddCurrency() { MyMoneySecurity curr("EUR", "Euro", "?", 100, 100); QCOMPARE(m->m_currencyList.count(), 0); m->m_dirty = false; try { m->addCurrency(curr); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->m_currencyList.count(), 1); QCOMPARE(m->m_currencyList["EUR"].name(), QLatin1String("Euro")); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } m->m_dirty = false; try { m->addCurrency(curr); QFAIL("Expected exception missing"); } catch (const MyMoneyException &) { m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), false); } } void MyMoneySeqAccessMgrTest::testModifyCurrency() { MyMoneySecurity curr("EUR", "Euro", "?", 100, 100); testAddCurrency(); m->m_dirty = false; curr.setName("EURO"); try { m->modifyCurrency(curr); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->m_currencyList.count(), 1); QCOMPARE(m->m_currencyList["EUR"].name(), QLatin1String("EURO")); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } m->m_dirty = false; MyMoneySecurity unknownCurr("DEM", "Deutsche Mark", "DM", 100, 100); try { m->modifyCurrency(unknownCurr); QFAIL("Expected exception missing"); } catch (const MyMoneyException &) { m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), false); } } void MyMoneySeqAccessMgrTest::testRemoveCurrency() { MyMoneySecurity curr("EUR", "Euro", "?", 100, 100); testAddCurrency(); m->m_dirty = false; try { m->removeCurrency(curr); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->m_currencyList.count(), 0); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } m->m_dirty = false; MyMoneySecurity unknownCurr("DEM", "Deutsche Mark", "DM", 100, 100); try { m->removeCurrency(unknownCurr); QFAIL("Expected exception missing"); } catch (const MyMoneyException &) { m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), false); } } void MyMoneySeqAccessMgrTest::testCurrency() { MyMoneySecurity curr("EUR", "Euro", "?", 100, 100); MyMoneySecurity newCurr; testAddCurrency(); m->m_dirty = false; try { newCurr = m->currency("EUR"); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), false); QCOMPARE(newCurr.id(), curr.id()); QCOMPARE(newCurr.name(), curr.name()); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } try { m->currency("DEM"); QFAIL("Expected exception missing"); } catch (const MyMoneyException &) { m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), false); } } void MyMoneySeqAccessMgrTest::testCurrencyList() { QCOMPARE(m->currencyList().count(), 0); testAddCurrency(); QCOMPARE(m->currencyList().count(), 1); MyMoneySecurity unknownCurr("DEM", "Deutsche Mark", "DM", 100, 100); try { m->addCurrency(unknownCurr); m->m_dirty = false; QCOMPARE(m->m_currencyList.count(), 2); QCOMPARE(m->currencyList().count(), 2); QCOMPARE(m->dirty(), false); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneySeqAccessMgrTest::testAccountList() { QList accounts; m->accountList(accounts); QCOMPARE(accounts.count(), 0); testAddNewAccount(); accounts.clear(); m->accountList(accounts); QCOMPARE(accounts.count(), 2); MyMoneyAccount a = m->account("A000001"); MyMoneyAccount b = m->account("A000002"); m->reparentAccount(b, a); accounts.clear(); m->accountList(accounts); QCOMPARE(accounts.count(), 2); } void MyMoneySeqAccessMgrTest::testLoaderFunctions() { // we don't need the transaction started by setup() here m->rollbackTransaction(); // account loader QMap amap; MyMoneyAccount acc("A0000176", MyMoneyAccount()); amap[acc.id()] = acc; m->loadAccounts(amap); QCOMPARE(m->m_accountList.values(), amap.values()); QCOMPARE(m->m_accountList.keys(), amap.keys()); QCOMPARE(m->m_nextAccountID, 176ul); // transaction loader QMap tmap; MyMoneyTransaction t("T000000108", MyMoneyTransaction()); tmap[t.id()] = t; m->loadTransactions(tmap); QCOMPARE(m->m_transactionList.values(), tmap.values()); QCOMPARE(m->m_transactionList.keys(), tmap.keys()); QCOMPARE(m->m_nextTransactionID, 108ul); // institution loader QMap imap; MyMoneyInstitution inst("I000028", MyMoneyInstitution()); imap[inst.id()] = inst; m->loadInstitutions(imap); QCOMPARE(m->m_institutionList.values(), imap.values()); QCOMPARE(m->m_institutionList.keys(), imap.keys()); QCOMPARE(m->m_nextInstitutionID, 28ul); // payee loader QMap pmap; MyMoneyPayee p("P1234", MyMoneyPayee()); pmap[p.id()] = p; m->loadPayees(pmap); QCOMPARE(m->m_payeeList.values(), pmap.values()); QCOMPARE(m->m_payeeList.keys(), pmap.keys()); QCOMPARE(m->m_nextPayeeID, 1234ul); // tag loader QMap tamap; MyMoneyTag ta("G1234", MyMoneyTag()); tamap[ta.id()] = ta; m->loadTags(tamap); QCOMPARE(m->m_tagList.values(), tamap.values()); QCOMPARE(m->m_tagList.keys(), tamap.keys()); QCOMPARE(m->m_nextTagID, 1234ul); // security loader QMap smap; MyMoneySecurity s("S54321", MyMoneySecurity()); smap[s.id()] = s; m->loadSecurities(smap); QCOMPARE(m->m_securitiesList.values(), smap.values()); QCOMPARE(m->m_securitiesList.keys(), smap.keys()); QCOMPARE(m->m_nextSecurityID, 54321ul); // schedule loader QMap schmap; MyMoneySchedule sch("SCH6789", MyMoneySchedule()); schmap[sch.id()] = sch; m->loadSchedules(schmap); QCOMPARE(m->m_scheduleList.values(), schmap.values()); QCOMPARE(m->m_scheduleList.keys(), schmap.keys()); QCOMPARE(m->m_nextScheduleID, 6789ul); // report loader QMap rmap; MyMoneyReport r("R1298", MyMoneyReport()); rmap[r.id()] = r; m->loadReports(rmap); QCOMPARE(m->m_reportList.values(), rmap.values()); QCOMPARE(m->m_reportList.keys(), rmap.keys()); QCOMPARE(m->m_nextReportID, 1298ul); // budget loader QMap bmap; MyMoneyBudget b("B89765", MyMoneyBudget()); bmap[b.id()] = b; m->loadBudgets(bmap); QCOMPARE(m->m_budgetList.values(), bmap.values()); QCOMPARE(m->m_budgetList.keys(), bmap.keys()); QCOMPARE(m->m_nextBudgetID, 89765ul); // restart a transaction so that teardown() is happy m->startTransaction(); } void MyMoneySeqAccessMgrTest::testAddOnlineJob() { // Add a onlineJob onlineJob job(new dummyTask()); m->addOnlineJob(job); QCOMPARE(job.id(), QString("O000001")); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->m_nextOnlineJobID, 1ul); QCOMPARE(m->dirty(), true); QCOMPARE(m->m_onlineJobList.count(), 1); QVERIFY(! m->m_onlineJobList["O000001"].isNull()); } diff --git a/kmymoney/mymoney/tests/mymoneyaccount-test.cpp b/kmymoney/mymoney/tests/mymoneyaccount-test.cpp index 4377c34dc..71c9e98b5 100644 --- a/kmymoney/mymoney/tests/mymoneyaccount-test.cpp +++ b/kmymoney/mymoney/tests/mymoneyaccount-test.cpp @@ -1,570 +1,570 @@ /*************************************************************************** mymoneyaccounttest.cpp ------------------- copyright : (C) 2002 by Thomas Baumgart email : ipwizard@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneyaccount-test.h" #include #include #include #define KMM_MYMONEY_UNIT_TESTABLE friend class MyMoneyAccountTest; #include "mymoneymoney.h" #include "mymoneyaccount.h" #include "mymoneyaccount_p.h" #include "mymoneysplit.h" #include "mymoneyexception.h" #include "mymoneyenums.h" QTEST_GUILESS_MAIN(MyMoneyAccountTest) void MyMoneyAccountTest::init() { } void MyMoneyAccountTest::cleanup() { } void MyMoneyAccountTest::testEmptyConstructor() { MyMoneyAccount a; QVERIFY(a.id().isEmpty()); QVERIFY(a.name().isEmpty()); QVERIFY(a.accountType() == eMyMoney::Account::Type::Unknown); QVERIFY(a.openingDate() == QDate()); QVERIFY(a.lastModified() == QDate()); QVERIFY(a.lastReconciliationDate() == QDate()); QVERIFY(a.accountList().count() == 0); QVERIFY(a.balance().isZero()); } void MyMoneyAccountTest::testConstructor() { QString id = "A000001"; QString institutionid = "B000001"; QString parent = "Parent"; MyMoneyAccount r; MyMoneySplit s; r.setAccountType(eMyMoney::Account::Type::Asset); r.setOpeningDate(QDate::currentDate()); r.setLastModified(QDate::currentDate()); r.setDescription("Desc"); r.setNumber("465500"); r.setParentAccountId(parent); r.setValue(QString("key"), "value"); s.setShares(MyMoneyMoney::ONE); r.adjustBalance(s); QVERIFY(r.pairs().count() == 1); QVERIFY(r.value("key") == "value"); MyMoneyAccount a(id, r); QVERIFY(a.id() == id); QVERIFY(a.institutionId().isEmpty()); QVERIFY(a.accountType() == eMyMoney::Account::Type::Asset); QVERIFY(a.openingDate() == QDate::currentDate()); QVERIFY(a.lastModified() == QDate::currentDate()); QVERIFY(a.number() == "465500"); QVERIFY(a.description() == "Desc"); QVERIFY(a.accountList().count() == 0); QVERIFY(a.parentAccountId() == "Parent"); QVERIFY(a.balance() == MyMoneyMoney::ONE); QMap copy; copy = r.pairs(); QVERIFY(copy.count() == 1); QVERIFY(copy[QString("key")] == "value"); } void MyMoneyAccountTest::testSetFunctions() { MyMoneyAccount a; QDate today(QDate::currentDate()); QVERIFY(a.name().isEmpty()); QVERIFY(a.lastModified() == QDate()); QVERIFY(a.description().isEmpty()); a.setName("Account"); a.setInstitutionId("Institution1"); a.setLastModified(today); a.setDescription("Desc"); a.setNumber("123456"); a.setAccountType(eMyMoney::Account::Type::MoneyMarket); QVERIFY(a.name() == "Account"); QVERIFY(a.institutionId() == "Institution1"); QVERIFY(a.lastModified() == today); QVERIFY(a.description() == "Desc"); QVERIFY(a.number() == "123456"); QVERIFY(a.accountType() == eMyMoney::Account::Type::MoneyMarket); } void MyMoneyAccountTest::testCopyConstructor() { QString id = "A000001"; QString institutionid = "B000001"; QString parent = "ParentAccount"; MyMoneyAccount r; r.setAccountType(eMyMoney::Account::Type::Expense); r.setOpeningDate(QDate::currentDate()); r.setLastModified(QDate::currentDate()); r.setName("Account"); r.setInstitutionId("Inst1"); r.setDescription("Desc1"); r.setNumber("Number"); r.setParentAccountId(parent); r.setValue("Key", "Value"); MyMoneyAccount a(id, r); a.setInstitutionId(institutionid); MyMoneyAccount b(a); QVERIFY(b.name() == "Account"); QVERIFY(b.institutionId() == institutionid); QVERIFY(b.accountType() == eMyMoney::Account::Type::Expense); QVERIFY(b.lastModified() == QDate::currentDate()); QVERIFY(b.openingDate() == QDate::currentDate()); QVERIFY(b.description() == "Desc1"); QVERIFY(b.number() == "Number"); QVERIFY(b.parentAccountId() == "ParentAccount"); QVERIFY(b.value("Key") == "Value"); } void MyMoneyAccountTest::testAssignmentConstructor() { MyMoneyAccount a; a.setAccountType(eMyMoney::Account::Type::Checkings); a.setName("Account"); a.setInstitutionId("Inst1"); a.setDescription("Bla"); a.setNumber("assigned Number"); a.setValue("Key", "Value"); a.addAccountId("ChildAccount"); MyMoneyAccount b; b.setLastModified(QDate::currentDate()); b = a; QVERIFY(b.name() == "Account"); QVERIFY(b.institutionId() == "Inst1"); QVERIFY(b.accountType() == eMyMoney::Account::Type::Checkings); QVERIFY(b.lastModified() == QDate()); QVERIFY(b.openingDate() == a.openingDate()); QVERIFY(b.description() == "Bla"); QVERIFY(b.number() == "assigned Number"); QVERIFY(b.value("Key") == "Value"); QVERIFY(b.accountList().count() == 1); QVERIFY(b.accountList()[0] == "ChildAccount"); } void MyMoneyAccountTest::testAdjustBalance() { MyMoneyAccount a; MyMoneySplit s; s.setShares(MyMoneyMoney(3, 1)); a.adjustBalance(s); QVERIFY(a.balance() == MyMoneyMoney(3, 1)); s.setShares(MyMoneyMoney(5, 1)); a.adjustBalance(s, true); QVERIFY(a.balance() == MyMoneyMoney(-2, 1)); s.setShares(MyMoneyMoney(2, 1)); s.setAction(MyMoneySplit::ActionSplitShares); a.adjustBalance(s); QVERIFY(a.balance() == MyMoneyMoney(-4, 1)); s.setShares(MyMoneyMoney(4, 1)); s.setAction(QString()); a.adjustBalance(s); QVERIFY(a.balance().isZero()); } void MyMoneyAccountTest::testSubAccounts() { MyMoneyAccount a; a.setAccountType(eMyMoney::Account::Type::Checkings); a.addAccountId("Subaccount1"); QVERIFY(a.accountList().count() == 1); a.addAccountId("Subaccount1"); QVERIFY(a.accountList().count() == 1); a.addAccountId("Subaccount2"); QVERIFY(a.accountList().count() == 2); } void MyMoneyAccountTest::testEquality() { MyMoneyAccount a; a.setLastModified(QDate::currentDate()); a.setName("Name"); a.setNumber("Number"); a.setDescription("Desc"); a.setInstitutionId("I-ID"); a.setOpeningDate(QDate::currentDate()); a.setLastReconciliationDate(QDate::currentDate()); a.setAccountType(eMyMoney::Account::Type::Asset); a.setParentAccountId("P-ID"); - a.setId("A-ID"); + a.d_func()->setId("A-ID"); a.setCurrencyId("C-ID"); a.setValue("Key", "Value"); MyMoneyAccount b; b = a; QVERIFY(b == a); a.setName("Noname"); QVERIFY(!(b == a)); b = a; a.setLastModified(QDate::currentDate().addDays(-1)); QVERIFY(!(b == a)); b = a; a.setNumber("Nonumber"); QVERIFY(!(b == a)); b = a; a.setDescription("NoDesc"); QVERIFY(!(b == a)); b = a; a.setInstitutionId("I-noID"); QVERIFY(!(b == a)); b = a; a.setOpeningDate(QDate::currentDate().addDays(-1)); QVERIFY(!(b == a)); b = a; a.setLastReconciliationDate(QDate::currentDate().addDays(-1)); QVERIFY(!(b == a)); b = a; a.setAccountType(eMyMoney::Account::Type::Liability); QVERIFY(!(b == a)); b = a; a.setParentAccountId("P-noID"); QVERIFY(!(b == a)); b = a; - a.setId("A-noID"); + a.d_func()->setId("A-noID"); QVERIFY(!(b == a)); b = a; a.setCurrencyId("C-noID"); QVERIFY(!(b == a)); b = a; a.setValue("Key", "noValue"); QVERIFY(!(b == a)); b = a; a.setValue("noKey", "Value"); QVERIFY(!(b == a)); b = a; } void MyMoneyAccountTest::testWriteXML() { QString id = "A000001"; QString institutionid = "B000001"; QString parent = "Parent"; MyMoneyAccount r; r.setAccountType(eMyMoney::Account::Type::Asset); r.setOpeningDate(QDate::currentDate()); r.setLastModified(QDate::currentDate()); r.setDescription("Desc"); r.setName("AccountName"); r.setNumber("465500"); r.setParentAccountId(parent); r.setInstitutionId(institutionid); r.setValue(QString("key"), "value"); r.addAccountId("A000002"); r.addReconciliation(QDate(2011, 1, 1), MyMoneyMoney(123, 100)); r.addReconciliation(QDate(2011, 2, 1), MyMoneyMoney(456, 100)); QCOMPARE(r.pairs().count(), 2); QCOMPARE(r.value("key"), QLatin1String("value")); QCOMPARE(r.value("reconciliationHistory"), QLatin1String("2011-01-01:123/100;2011-02-01:114/25")); MyMoneyAccount a(id, r); QDomDocument doc("TEST"); QDomElement el = doc.createElement("ACCOUNT-CONTAINER"); doc.appendChild(el); a.writeXML(doc, el); QCOMPARE(doc.doctype().name(), QLatin1String("TEST")); QDomElement accountContainer = doc.documentElement(); QVERIFY(accountContainer.isElement()); QCOMPARE(accountContainer.tagName(), QLatin1String("ACCOUNT-CONTAINER")); QVERIFY(accountContainer.childNodes().size() == 1); QVERIFY(accountContainer.childNodes().at(0).isElement()); QDomElement account = accountContainer.childNodes().at(0).toElement(); QCOMPARE(account.tagName(), QLatin1String("ACCOUNT")); QCOMPARE(account.attribute("id"), QLatin1String("A000001")); QCOMPARE(account.attribute("lastreconciled"), QString()); QCOMPARE(account.attribute("institution"), QLatin1String("B000001")); QCOMPARE(account.attribute("name"), QLatin1String("AccountName")); QCOMPARE(account.attribute("number"), QLatin1String("465500")); QCOMPARE(account.attribute("description"), QLatin1String("Desc")); QCOMPARE(account.attribute("parentaccount"), QLatin1String("Parent")); QCOMPARE(account.attribute("opened"), QDate::currentDate().toString(Qt::ISODate)); QCOMPARE(account.attribute("type"), QLatin1String("9")); QCOMPARE(account.attribute("lastmodified"), QDate::currentDate().toString(Qt::ISODate)); QCOMPARE(account.attribute("id"), QLatin1String("A000001")); QCOMPARE(account.childNodes().size(), 2); QVERIFY(account.childNodes().at(0).isElement()); QDomElement subAccounts = account.childNodes().at(0).toElement(); QCOMPARE(subAccounts.tagName(), QLatin1String("SUBACCOUNTS")); QCOMPARE(subAccounts.childNodes().size(), 1); QVERIFY(subAccounts.childNodes().at(0).isElement()); QDomElement subAccount = subAccounts.childNodes().at(0).toElement(); QCOMPARE(subAccount.tagName(), QLatin1String("SUBACCOUNT")); QCOMPARE(subAccount.attribute("id"), QLatin1String("A000002")); QCOMPARE(subAccount.childNodes().size(), 0); QDomElement keyValuePairs = account.childNodes().at(1).toElement(); QCOMPARE(keyValuePairs.tagName(), QLatin1String("KEYVALUEPAIRS")); QCOMPARE(keyValuePairs.childNodes().size(), 2); QVERIFY(keyValuePairs.childNodes().at(0).isElement()); QDomElement keyValuePair1 = keyValuePairs.childNodes().at(0).toElement(); QCOMPARE(keyValuePair1.tagName(), QLatin1String("PAIR")); QCOMPARE(keyValuePair1.attribute("key"), QLatin1String("key")); QCOMPARE(keyValuePair1.attribute("value"), QLatin1String("value")); QCOMPARE(keyValuePair1.childNodes().size(), 0); QVERIFY(keyValuePairs.childNodes().at(1).isElement()); QDomElement keyValuePair2 = keyValuePairs.childNodes().at(1).toElement(); QCOMPARE(keyValuePair2.tagName(), QLatin1String("PAIR")); QCOMPARE(keyValuePair2.attribute("key"), QLatin1String("reconciliationHistory")); QCOMPARE(keyValuePair2.attribute("value"), QLatin1String("2011-01-01:123/100;2011-02-01:114/25")); QCOMPARE(keyValuePair2.childNodes().size(), 0); } void MyMoneyAccountTest::testReadXML() { MyMoneyAccount a; QString ref_ok = QString( "\n" "\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n"). arg(QDate::currentDate().toString(Qt::ISODate)).arg(QDate::currentDate().toString(Qt::ISODate)); QString ref_false = QString( "\n" "\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n"). arg(QDate::currentDate().toString(Qt::ISODate)).arg(QDate::currentDate().toString(Qt::ISODate)); QDomDocument doc; QDomElement node; doc.setContent(ref_false); node = doc.documentElement().firstChild().toElement(); try { a = MyMoneyAccount(node); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { } doc.setContent(ref_ok); node = doc.documentElement().firstChild().toElement(); a.addAccountId("TEST"); a.setValue("KEY", "VALUE"); try { a = MyMoneyAccount(node); QVERIFY(a.id() == "A000001"); QVERIFY(a.name() == "AccountName"); QVERIFY(a.parentAccountId() == "Parent"); QVERIFY(a.lastModified() == QDate::currentDate()); QVERIFY(a.lastReconciliationDate() == QDate()); QVERIFY(a.institutionId() == "B000001"); QVERIFY(a.number() == "465500"); QVERIFY(a.openingDate() == QDate::currentDate()); QVERIFY(a.accountType() == eMyMoney::Account::Type::Asset); QVERIFY(a.description() == "Desc"); QVERIFY(a.accountList().count() == 2); QVERIFY(a.accountList()[0] == "A000002"); QVERIFY(a.accountList()[1] == "A000003"); QVERIFY(a.pairs().count() == 4); QVERIFY(a.value("key") == "value"); QVERIFY(a.value("Key") == "Value"); QVERIFY(a.value("lastStatementDate").isEmpty()); QVERIFY(a.reconciliationHistory().count() == 2); QVERIFY(a.reconciliationHistory()[QDate(2011, 1, 1)] == MyMoneyMoney(123, 100)); QVERIFY(a.reconciliationHistory()[QDate(2011, 2, 1)] == MyMoneyMoney(456, 100)); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyAccountTest::testHasReferenceTo() { MyMoneyAccount a; a.setInstitutionId("I0001"); a.addAccountId("A_001"); a.addAccountId("A_002"); a.setParentAccountId("A_Parent"); a.setCurrencyId("Currency"); QVERIFY(a.hasReferenceTo("I0001") == true); QVERIFY(a.hasReferenceTo("I0002") == false); QVERIFY(a.hasReferenceTo("A_001") == false); QVERIFY(a.hasReferenceTo("A_Parent") == true); QVERIFY(a.hasReferenceTo("Currency") == true); } void MyMoneyAccountTest::testSetClosed() { MyMoneyAccount a; QVERIFY(a.isClosed() == false); a.setClosed(true); QVERIFY(a.isClosed() == true); a.setClosed(false); QVERIFY(a.isClosed() == false); } void MyMoneyAccountTest::specialAccountTypes_data() { QTest::addColumn("accountType"); QTest::addColumn("incomeExpense"); QTest::addColumn("assetLibility"); QTest::addColumn("loan"); // positive and null is debit QTest::newRow("unknown") << eMyMoney::Account::Type::Unknown << false << false << false; QTest::newRow("checking") << eMyMoney::Account::Type::Checkings << false << true << false; QTest::newRow("savings") << eMyMoney::Account::Type::Savings << false << true << false; QTest::newRow("cash") << eMyMoney::Account::Type::Cash << false << true << false; QTest::newRow("investment") << eMyMoney::Account::Type::Investment << false << true << false; QTest::newRow("asset") << eMyMoney::Account::Type::Asset << false << true << false; QTest::newRow("currency") << eMyMoney::Account::Type::Currency << false << true << false; QTest::newRow("expense") << eMyMoney::Account::Type::Expense << true << false << false; QTest::newRow("moneymarket") << eMyMoney::Account::Type::MoneyMarket << false << true << false; QTest::newRow("certificatedeposit") << eMyMoney::Account::Type::CertificateDep << false << true << false; QTest::newRow("assetloan") << eMyMoney::Account::Type::AssetLoan << false << true << true; QTest::newRow("stock") << eMyMoney::Account::Type::Stock << false << true << false; QTest::newRow("creditcard") << eMyMoney::Account::Type::CreditCard << false << true << false; QTest::newRow("loan") << eMyMoney::Account::Type::Loan << false << true << true; QTest::newRow("liability") << eMyMoney::Account::Type::Liability << false << true << false; QTest::newRow("income") << eMyMoney::Account::Type::Income << true << false << false; QTest::newRow("equity") << eMyMoney::Account::Type::Equity << false << false << false; } void MyMoneyAccountTest::specialAccountTypes() { QFETCH(eMyMoney::Account::Type, accountType); QFETCH(bool, incomeExpense); QFETCH(bool, assetLibility); QFETCH(bool, loan); MyMoneyAccount a; a.setAccountType(accountType); QCOMPARE(a.isIncomeExpense(), incomeExpense); QCOMPARE(a.isAssetLiability(), assetLibility); QCOMPARE(a.isLoan(), loan); } void MyMoneyAccountTest::addReconciliation() { MyMoneyAccount a; QVERIFY(a.addReconciliation(QDate(2011, 1, 2), MyMoneyMoney(123, 100)) == true); QVERIFY(a.reconciliationHistory().count() == 1); QVERIFY(a.addReconciliation(QDate(2011, 2, 1), MyMoneyMoney(456, 100)) == true); QVERIFY(a.reconciliationHistory().count() == 2); QVERIFY(a.addReconciliation(QDate(2011, 2, 1), MyMoneyMoney(789, 100)) == true); QVERIFY(a.reconciliationHistory().count() == 2); QVERIFY(a.reconciliationHistory().values().last() == MyMoneyMoney(789, 100)); } void MyMoneyAccountTest::reconciliationHistory() { MyMoneyAccount a; QVERIFY(a.reconciliationHistory().isEmpty() == true); QVERIFY(a.addReconciliation(QDate(2011, 1, 2), MyMoneyMoney(123, 100)) == true); QVERIFY(a.reconciliationHistory()[QDate(2011, 1, 2)] == MyMoneyMoney(123, 100)); QVERIFY(a.reconciliationHistory()[QDate(2011, 1, 1)] == MyMoneyMoney()); QVERIFY(a.reconciliationHistory()[QDate(2011, 1, 3)] == MyMoneyMoney()); QVERIFY(a.addReconciliation(QDate(2011, 2, 1), MyMoneyMoney(456, 100)) == true); QVERIFY(a.reconciliationHistory()[QDate(2011, 1, 2)] == MyMoneyMoney(123, 100)); QVERIFY(a.reconciliationHistory()[QDate(2011, 1, 1)] == MyMoneyMoney()); QVERIFY(a.reconciliationHistory()[QDate(2011, 1, 3)] == MyMoneyMoney()); QVERIFY(a.reconciliationHistory()[QDate(2011, 2, 1)] == MyMoneyMoney(456, 100)); QVERIFY(a.reconciliationHistory().count() == 2); } void MyMoneyAccountTest::testElementNames() { for (auto i = (int)Account::Element::SubAccount; i <= (int)Account::Element::OnlineBanking; ++i) { auto isEmpty = MyMoneyAccountPrivate::getElName(static_cast(i)).isEmpty(); if (isEmpty) qWarning() << "Empty element's name " << i; QVERIFY(!isEmpty); } } void MyMoneyAccountTest::testAttributeNames() { for (auto i = (int)Account::Attribute::ID; i < (int)Account::Attribute::LastAttribute; ++i) { auto isEmpty = MyMoneyAccountPrivate::getAttrName(static_cast(i)).isEmpty(); if (isEmpty) qWarning() << "Empty attribute's name " << i; QVERIFY(!isEmpty); } } diff --git a/kmymoney/mymoney/tests/mymoneyobject-test.cpp b/kmymoney/mymoney/tests/mymoneyobject-test.cpp index 01defd7af..e1e93aca1 100644 --- a/kmymoney/mymoney/tests/mymoneyobject-test.cpp +++ b/kmymoney/mymoney/tests/mymoneyobject-test.cpp @@ -1,161 +1,185 @@ /*************************************************************************** mymoneyobjecttest.cpp ------------------- copyright : (C) 2005 by Thomas Baumgart email : ipwizard@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneyobject-test.h" #include #include #include +#include "mymoneyobject_p.h" #include "mymoneyexception.h" #include "mymoneyaccount.h" +class TestMyMoneyObjectPrivate : public MyMoneyObjectPrivate +{ +public: + TestMyMoneyObjectPrivate() + { + } +}; class TestMyMoneyObject : public MyMoneyObject { + Q_DECLARE_PRIVATE(TestMyMoneyObject) public: - TestMyMoneyObject() : MyMoneyObject() {} + TestMyMoneyObject() : MyMoneyObject(*new MyMoneyObjectPrivate) {} + TestMyMoneyObject & operator=(TestMyMoneyObject other); + friend void swap(TestMyMoneyObject& first, TestMyMoneyObject& second); + ~TestMyMoneyObject(){} TestMyMoneyObject(const QDomElement& node, const bool forceId = true) : - MyMoneyObject(node, forceId) {} + MyMoneyObject(*new MyMoneyObjectPrivate, node, forceId) {} virtual bool hasReferenceTo(const QString&) const { return false; } virtual void writeXML(QDomDocument&, QDomElement&) const {} }; +void swap(TestMyMoneyObject& first, TestMyMoneyObject& second) +{ + using std::swap; + swap(first.d_ptr, second.d_ptr); +} + +TestMyMoneyObject & TestMyMoneyObject::operator=(TestMyMoneyObject other) +{ + swap(*this, other); + return *this; +} + QTEST_GUILESS_MAIN(MyMoneyObjectTest) void MyMoneyObjectTest::testEmptyConstructor() { MyMoneyAccount a; QVERIFY(a.id().isEmpty()); } void MyMoneyObjectTest::testConstructor() { MyMoneyAccount a(QString("thb"), MyMoneyAccount()); QVERIFY(!a.id().isEmpty()); QVERIFY(a.id() == QString("thb")); } void MyMoneyObjectTest::testClearId() { MyMoneyAccount a(QString("thb"), MyMoneyAccount()); QVERIFY(!a.id().isEmpty()); a.clearId(); QVERIFY(a.id().isEmpty()); } void MyMoneyObjectTest::testCopyConstructor() { MyMoneyAccount a(QString("thb"), MyMoneyAccount()); MyMoneyAccount b(a); QVERIFY(a.MyMoneyObject::operator==(b)); } void MyMoneyObjectTest::testAssignmentConstructor() { MyMoneyAccount a(QString("thb"), MyMoneyAccount()); MyMoneyAccount b = a; QVERIFY(a.MyMoneyObject::operator==(b)); } void MyMoneyObjectTest::testEquality() { MyMoneyAccount a(QString("thb"), MyMoneyAccount()); MyMoneyAccount b(QString("thb"), MyMoneyAccount()); MyMoneyAccount c(QString("ace"), MyMoneyAccount()); QVERIFY(a.MyMoneyObject::operator==(b)); QVERIFY(!(a.MyMoneyObject::operator==(c))); } void MyMoneyObjectTest::testReadXML() { TestMyMoneyObject t; QString ref_ok = QString( "\n" "\n" " \n" " \n" "\n" ); QString ref_false1 = QString( "\n" "\n" " \n" " \n" "\n" ); QString ref_false2 = QString( "\n" "\n" " \n" " \n" "\n" ); QDomDocument doc; QDomElement node; // id="" but required doc.setContent(ref_false1); node = doc.documentElement().firstChild().toElement(); try { t = TestMyMoneyObject(node); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { } // id attribute missing but required doc.setContent(ref_false2); node = doc.documentElement().firstChild().toElement(); try { t = TestMyMoneyObject(node); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { } // id present doc.setContent(ref_ok); node = doc.documentElement().firstChild().toElement(); try { t = TestMyMoneyObject(node); QVERIFY(t.id() == "T000000000000000001"); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } // id="" but not required doc.setContent(ref_false1); node = doc.documentElement().firstChild().toElement(); try { t = TestMyMoneyObject(node, false); QVERIFY(t.id().isEmpty()); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } diff --git a/kmymoney/mymoney/tests/mymoneypayee-test.cpp b/kmymoney/mymoney/tests/mymoneypayee-test.cpp index d71adea9a..e7aa837c9 100644 --- a/kmymoney/mymoney/tests/mymoneypayee-test.cpp +++ b/kmymoney/mymoney/tests/mymoneypayee-test.cpp @@ -1,229 +1,229 @@ /*************************************************************************** mymoneypayeetest.cpp ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneypayee-test.h" #include #include #include #define KMM_MYMONEY_UNIT_TESTABLE friend class MyMoneyPayeeTest; #include "mymoneypayee.h" #include "mymoneypayee_p.h" using namespace std; QTEST_GUILESS_MAIN(MyMoneyPayeeTest) void MyMoneyPayeeTest::testXml() { QDomDocument doc; QDomElement parent = doc.createElement("Test"); doc.appendChild(parent); MyMoneyPayee payee1; - payee1.m_id = "some random id";//if the ID isn't set, w ethrow an exception + payee1.d_func()->m_id = "some random id";//if the ID isn't set, w ethrow an exception payee1.writeXML(doc, parent); QString temp1 = "Account1"; payee1.setDefaultAccountId(temp1); payee1.writeXML(doc, parent); QString temp2 = "Account2"; payee1.setDefaultAccountId(temp2); payee1.writeXML(doc, parent); payee1.setDefaultAccountId(); payee1.writeXML(doc, parent); QDomElement el = parent.firstChild().toElement(); QVERIFY(!el.isNull()); MyMoneyPayee payee2(el); QVERIFY(!payee2.defaultAccountEnabled()); QVERIFY(payee2.defaultAccountId().isEmpty()); el = el.nextSibling().toElement(); QVERIFY(!el.isNull()); MyMoneyPayee payee3(el); QVERIFY(payee3.defaultAccountEnabled()); QVERIFY(payee3.defaultAccountId() == temp1); el = el.nextSibling().toElement(); QVERIFY(!el.isNull()); MyMoneyPayee payee4(el); QVERIFY(payee4.defaultAccountEnabled()); QVERIFY(payee4.defaultAccountId() == temp2); el = el.nextSibling().toElement(); QVERIFY(!el.isNull()); MyMoneyPayee payee5(el); QVERIFY(!payee5.defaultAccountEnabled()); QVERIFY(payee5.defaultAccountId().isEmpty()); } void MyMoneyPayeeTest::testDefaultAccount() { MyMoneyPayee payee; QVERIFY(!payee.defaultAccountEnabled()); QVERIFY(payee.defaultAccountId().isEmpty()); QString temp = "Account1"; payee.setDefaultAccountId(temp); QVERIFY(payee.defaultAccountEnabled()); QVERIFY(payee.defaultAccountId() == temp); payee.setDefaultAccountId(); QVERIFY(!payee.defaultAccountEnabled()); QVERIFY(payee.defaultAccountId().isEmpty()); } void MyMoneyPayeeTest::testEmptyMatchKeyBegin() { MyMoneyPayee payee; QString keys; bool ignoreCase; payee.setMatchData(MyMoneyPayee::matchKey, false, "\ntest1\ntest2"); QVERIFY(payee.matchData(ignoreCase, keys) == MyMoneyPayee::matchKey); QVERIFY(ignoreCase == false); QVERIFY(keys == QLatin1String("test1\ntest2")); payee.setMatchData(MyMoneyPayee::matchKey, false, "\n\ntest1\ntest2"); QVERIFY(payee.matchData(ignoreCase, keys) == MyMoneyPayee::matchKey); QVERIFY(ignoreCase == false); QVERIFY(keys == QLatin1String("test1\ntest2")); } void MyMoneyPayeeTest::testEmptyMatchKeyEnd() { MyMoneyPayee payee; QString keys; bool ignoreCase; payee.setMatchData(MyMoneyPayee::matchKey, false, "test1\ntest2\n"); QVERIFY(payee.matchData(ignoreCase, keys) == MyMoneyPayee::matchKey); QVERIFY(ignoreCase == false); QVERIFY(keys == QLatin1String("test1\ntest2")); payee.setMatchData(MyMoneyPayee::matchKey, false, "test1\ntest2\n\n"); QVERIFY(payee.matchData(ignoreCase, keys) == MyMoneyPayee::matchKey); QVERIFY(ignoreCase == false); QVERIFY(keys == QLatin1String("test1\ntest2")); } void MyMoneyPayeeTest::testEmptyMatchKeyMiddle() { MyMoneyPayee payee; QString keys; bool ignoreCase; payee.setMatchData(MyMoneyPayee::matchKey, false, "test1\n\ntest2"); QVERIFY(payee.matchData(ignoreCase, keys) == MyMoneyPayee::matchKey); QVERIFY(ignoreCase == false); QVERIFY(keys == QLatin1String("test1\ntest2")); payee.setMatchData(MyMoneyPayee::matchKey, false, "test1\n\n\ntest2"); QVERIFY(payee.matchData(ignoreCase, keys) == MyMoneyPayee::matchKey); QVERIFY(ignoreCase == false); QVERIFY(keys == QLatin1String("test1\ntest2")); } void MyMoneyPayeeTest::testEmptyMatchKeyMix() { MyMoneyPayee payee; QString keys; bool ignoreCase; payee.setMatchData(MyMoneyPayee::matchKey, false, "\ntest1\n\ntest2\n"); QVERIFY(payee.matchData(ignoreCase, keys) == MyMoneyPayee::matchKey); QVERIFY(ignoreCase == false); QVERIFY(keys == QLatin1String("test1\ntest2")); payee.setMatchData(MyMoneyPayee::matchKey, false, "\n\ntest1\n\n\ntest2\n\n"); QVERIFY(payee.matchData(ignoreCase, keys) == MyMoneyPayee::matchKey); QVERIFY(ignoreCase == false); QVERIFY(keys == QLatin1String("test1\ntest2")); } void MyMoneyPayeeTest::testMatchKeyDisallowSingleSpace() { MyMoneyPayee payee; QString keys; bool ignoreCase; payee.setMatchData(MyMoneyPayee::matchKey, false, " "); QVERIFY(payee.matchData(ignoreCase, keys) == MyMoneyPayee::matchKey); QVERIFY(ignoreCase == false); QVERIFY(keys == QLatin1String("")); } void MyMoneyPayeeTest::testMatchKeyDisallowMultipleSpace() { MyMoneyPayee payee; QString keys; bool ignoreCase; payee.setMatchData(MyMoneyPayee::matchKey, false, " "); QVERIFY(payee.matchData(ignoreCase, keys) == MyMoneyPayee::matchKey); QVERIFY(ignoreCase == false); QVERIFY(keys == QLatin1String("")); } void MyMoneyPayeeTest::testMatchKeyAllowSpaceAtStart() { MyMoneyPayee payee; QString keys; bool ignoreCase; payee.setMatchData(MyMoneyPayee::matchKey, false, " payee"); QVERIFY(payee.matchData(ignoreCase, keys) == MyMoneyPayee::matchKey); QVERIFY(ignoreCase == false); QVERIFY(keys == QLatin1String(" payee")); } void MyMoneyPayeeTest::testMatchKeyAllowSpaceAtEnd() { MyMoneyPayee payee; QString keys; bool ignoreCase; payee.setMatchData(MyMoneyPayee::matchKey, false, "payee "); QVERIFY(payee.matchData(ignoreCase, keys) == MyMoneyPayee::matchKey); QVERIFY(ignoreCase == false); QVERIFY(keys == QLatin1String("payee ")); } void MyMoneyPayeeTest::testMatchNameExact() { MyMoneyPayee payee; QString keys; bool ignoreCase; payee.setMatchData(MyMoneyPayee::matchNameExact, false, keys); keys = QLatin1String("payee "); QCOMPARE(payee.matchData(ignoreCase, keys), MyMoneyPayee::matchNameExact); QCOMPARE(ignoreCase, false); QVERIFY(keys.isEmpty()); } void MyMoneyPayeeTest::testElementNames() { for (auto i = (int)Payee::Element::Address; i <= (int)Payee::Element::Address; ++i) { auto isEmpty = MyMoneyPayeePrivate::getElName(static_cast(i)).isEmpty(); if (isEmpty) qWarning() << "Empty element's name " << i; QVERIFY(!isEmpty); } } void MyMoneyPayeeTest::testAttributeNames() { for (auto i = (int)Payee::Attribute::Name; i < (int)Payee::Attribute::LastAttribute; ++i) { auto isEmpty = MyMoneyPayeePrivate::getAttrName(static_cast(i)).isEmpty(); if (isEmpty) qWarning() << "Empty attribute's name " << i; QVERIFY(!isEmpty); } } diff --git a/kmymoney/mymoney/tests/mymoneyschedule-test.cpp b/kmymoney/mymoney/tests/mymoneyschedule-test.cpp index 7fe46c9e7..853f8241c 100644 --- a/kmymoney/mymoney/tests/mymoneyschedule-test.cpp +++ b/kmymoney/mymoney/tests/mymoneyschedule-test.cpp @@ -1,1777 +1,1778 @@ /*************************************************************************** mymoneyscheduletest.cpp ------------------- copyright : (C) 2002 by Michael Edwardes email : mte@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneyschedule-test.h" #include #include #include #include #define KMM_MYMONEY_UNIT_TESTABLE friend class MyMoneyScheduleTest; #include "mymoneysplit.h" #include "mymoneymoney.h" #include "mymoneyschedule.h" #include "mymoneyschedule_p.h" #include "mymoneyfile.h" #include "mymoneytransaction.h" +#include "mymoneytransaction_p.h" #include "storage/mymoneyseqaccessmgr.h" QTEST_GUILESS_MAIN(MyMoneyScheduleTest) using namespace eMyMoney; void MyMoneyScheduleTest::testEmptyConstructor() { MyMoneySchedule s; QCOMPARE(s.id().isEmpty(), true); QCOMPARE(s.occurrence(), Schedule::Occurrence::Any); QCOMPARE(s.type(), Schedule::Type::Any); QCOMPARE(s.paymentType(), Schedule::PaymentType::Any); QCOMPARE(s.isFinished(), false); QCOMPARE(!s.startDate().isValid(), true); QCOMPARE(!s.endDate().isValid(), true); QCOMPARE(!s.lastPayment().isValid(), true); QCOMPARE(s.autoEnter(), false); QCOMPARE(s.name().isEmpty(), true); QCOMPARE(s.willEnd(), false); QCOMPARE(s.lastDayInMonth(), false); } void MyMoneyScheduleTest::testConstructor() { MyMoneySchedule s("A Name", Schedule::Type::Bill, Schedule::Occurrence::Weekly, 1, Schedule::PaymentType::DirectDebit, QDate::currentDate(), QDate(), true, true); QCOMPARE(s.type(), Schedule::Type::Bill); QCOMPARE(s.occurrence(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrenceMultiplier(), 1); QCOMPARE(s.paymentType(), Schedule::PaymentType::DirectDebit); QCOMPARE(s.startDate(), QDate()); QCOMPARE(s.willEnd(), false); QCOMPARE(s.isFixed(), true); QCOMPARE(s.autoEnter(), true); QCOMPARE(s.name(), QLatin1String("A Name")); QCOMPARE(!s.endDate().isValid(), true); QCOMPARE(!s.lastPayment().isValid(), true); } void MyMoneyScheduleTest::testSetFunctions() { MyMoneySchedule s; - s.setId("SCHED001"); + s.d_func()->setId("SCHED001"); QCOMPARE(s.id(), QLatin1String("SCHED001")); s.setType(Schedule::Type::Bill); QCOMPARE(s.type(), Schedule::Type::Bill); s.setEndDate(QDate::currentDate()); QCOMPARE(s.endDate(), QDate::currentDate()); QCOMPARE(s.willEnd(), true); } void MyMoneyScheduleTest::testCopyConstructor() { MyMoneySchedule s; - s.setId("SCHED001"); + s.d_func()->setId("SCHED001"); s.setType(Schedule::Type::Bill); MyMoneySchedule s2(s); QCOMPARE(s.id(), s2.id()); QCOMPARE(s.type(), s2.type()); } void MyMoneyScheduleTest::testAssignmentConstructor() { MyMoneySchedule s; - s.setId("SCHED001"); + s.d_func()->setId("SCHED001"); s.setType(Schedule::Type::Bill); MyMoneySchedule s2 = s; QCOMPARE(s.id(), s2.id()); QCOMPARE(s.type(), s2.type()); } void MyMoneyScheduleTest::testOverdue() { MyMoneySchedule sch_overdue; MyMoneySchedule sch_intime; // the following checks only work correctly, if currentDate() is // between the 1st and 27th. If it is between 28th and 31st // we don't perform them. Note: this should be fixed. if (QDate::currentDate().day() > 27 || QDate::currentDate().day() == 1) { qDebug() << "testOverdue() skipped because current day is between 28th and 2nd"; return; } QDate startDate = QDate::currentDate().addDays(-1).addMonths(-23); QDate lastPaymentDate = QDate::currentDate().addDays(-1).addMonths(-1); QString ref = QString( "\n" "\n" " \n" // krazy:exclude=spelling " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n"); QString ref_overdue = ref.arg(startDate.toString(Qt::ISODate)) .arg(lastPaymentDate.toString(Qt::ISODate)) .arg(lastPaymentDate.toString(Qt::ISODate)); QString ref_intime = ref.arg(startDate.addDays(1).toString(Qt::ISODate)) .arg(lastPaymentDate.addDays(1).toString(Qt::ISODate)) .arg(lastPaymentDate.addDays(1).toString(Qt::ISODate)); QDomDocument doc; QDomElement node; // std::cout << ref_intime << std::endl; try { doc.setContent(ref_overdue); node = doc.documentElement().firstChild().toElement(); sch_overdue = MyMoneySchedule(node); doc.setContent(ref_intime); node = doc.documentElement().firstChild().toElement(); sch_intime = MyMoneySchedule(node); QCOMPARE(sch_overdue.isOverdue(), true); QCOMPARE(sch_intime.isOverdue(), false); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyScheduleTest::testNextPayment() /* * Test for a schedule where a payment hasn't yet been made. * First payment is in the future. */ { MyMoneySchedule sch; QString future_sched = QString( "\n" "\n" "\n" // krazy:exclude=spelling " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" "\n" ); QDomDocument doc; QDomElement node; doc.setContent(future_sched); node = doc.documentElement().firstChild().toElement(); try { sch = MyMoneySchedule(node); QCOMPARE(sch.nextPayment(QDate(2007, 2, 14)), QDate(2007, 2, 17)); QCOMPARE(sch.nextPayment(QDate(2007, 2, 17)), QDate(2008, 2, 17)); QCOMPARE(sch.nextPayment(QDate(2007, 2, 18)), QDate(2008, 2, 17)); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyScheduleTest::testNextPaymentOnLastDayOfMonth() { MyMoneySchedule sch; QString future_sched = QString( "\n" "\n" "\n" // krazy:exclude=spelling " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" "\n" ); QDomDocument doc; QDomElement node; doc.setContent(future_sched); node = doc.documentElement().firstChild().toElement(); try { sch = MyMoneySchedule(node); QDate nextPayment; // check for the first payment to happen nextPayment = sch.nextPayment(QDate(2014, 10, 1)); QCOMPARE(nextPayment, QDate(2014, 10, 31)); sch.setLastPayment(nextPayment); QCOMPARE(sch.nextPayment(QDate(2014, 11, 1)), QDate(2014, 11, 30)); QCOMPARE(sch.nextPayment(QDate(2014, 12, 1)), QDate(2014, 12, 31)); QCOMPARE(sch.nextPayment(QDate(2015, 1, 1)), QDate(2015, 1, 31)); QCOMPARE(sch.nextPayment(QDate(2015, 2, 1)), QDate(2015, 2, 28)); QCOMPARE(sch.nextPayment(QDate(2015, 3, 1)), QDate(2015, 3, 31)); // now check that we also cover leap years QCOMPARE(sch.nextPayment(QDate(2016, 2, 1)), QDate(2016, 2, 29)); QCOMPARE(sch.nextPayment(QDate(2016, 3, 1)), QDate(2016, 3, 31)); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyScheduleTest::testAddHalfMonths() { // addHalfMonths is private // Test a Schedule with occurrence EveryHalfMonth using nextPayment MyMoneySchedule s; s.setStartDate(QDate(2007, 1, 1)); s.setOccurrence(Schedule::Occurrence::EveryHalfMonth); s.setNextDueDate(s.startDate()); s.setLastPayment(s.startDate()); QString format("yyyy-MM-dd"); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-16")); s.setNextDueDate(QDate(2007, 1, 2)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-17")); s.setNextDueDate(QDate(2007, 1, 3)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-18")); s.setNextDueDate(QDate(2007, 1, 4)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-19")); s.setNextDueDate(QDate(2007, 1, 5)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-20")); s.setNextDueDate(QDate(2007, 1, 6)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-21")); s.setNextDueDate(QDate(2007, 1, 7)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-22")); s.setNextDueDate(QDate(2007, 1, 8)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-23")); s.setNextDueDate(QDate(2007, 1, 9)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-24")); s.setNextDueDate(QDate(2007, 1, 10)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-25")); s.setNextDueDate(QDate(2007, 1, 11)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-26")); s.setNextDueDate(QDate(2007, 1, 12)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-27")); s.setNextDueDate(QDate(2007, 1, 13)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-28")); s.setNextDueDate(QDate(2007, 1, 14)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-29")); // 15 -> Last Day s.setNextDueDate(QDate(2007, 1, 15)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-31")); s.setNextDueDate(QDate(2007, 1, 16)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-01")); s.setNextDueDate(QDate(2007, 1, 17)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-02")); s.setNextDueDate(QDate(2007, 1, 18)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-03")); s.setNextDueDate(QDate(2007, 1, 19)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-04")); s.setNextDueDate(QDate(2007, 1, 20)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-05")); s.setNextDueDate(QDate(2007, 1, 21)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-06")); s.setNextDueDate(QDate(2007, 1, 22)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-07")); s.setNextDueDate(QDate(2007, 1, 23)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-08")); s.setNextDueDate(QDate(2007, 1, 24)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-09")); s.setNextDueDate(QDate(2007, 1, 25)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-10")); s.setNextDueDate(QDate(2007, 1, 26)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-11")); s.setNextDueDate(QDate(2007, 1, 27)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-12")); s.setNextDueDate(QDate(2007, 1, 28)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-13")); s.setNextDueDate(QDate(2007, 1, 29)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-14")); // 30th,31st -> 15th s.setNextDueDate(QDate(2007, 1, 30)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-15")); s.setNextDueDate(QDate(2007, 1, 31)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-15")); // 30th (last day) s.setNextDueDate(QDate(2007, 4, 30)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-05-15")); // 28th of February (Last day): to 15th s.setNextDueDate(QDate(1900, 2, 28)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("1900-03-15")); // 28th of February (Leap year): to 13th s.setNextDueDate(QDate(2000, 2, 28)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2000-03-13")); // 29th of February (Leap year) s.setNextDueDate(QDate(2000, 2, 29)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2000-03-15")); // Add multiple transactions s.setStartDate(QDate(2007, 1, 1)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-01-16")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-02-01")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-02-16")); s.setStartDate(QDate(2007, 1, 12)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-01-27")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-02-12")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-02-27")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-03-12")); s.setStartDate(QDate(2007, 1, 13)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-01-28")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-02-13")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-02-28")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-03-15")); s.setStartDate(QDate(2007, 1, 14)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-01-29")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-02-14")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-02-28")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-03-15")); s.setStartDate(QDate(2007, 1, 15)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-01-31")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-02-15")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-02-28")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-03-15")); s.setStartDate(QDate(2007, 1, 16)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-02-01")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-02-16")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-03-01")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-03-16")); s.setStartDate(QDate(2007, 1, 27)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-02-12")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-02-27")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-03-12")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-03-27")); s.setStartDate(QDate(2007, 1, 28)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-02-13")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-02-28")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-03-15")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-03-31")); s.setStartDate(QDate(2007, 1, 29)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-02-14")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-02-28")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-03-15")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-03-31")); s.setStartDate(QDate(2007, 1, 30)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-02-15")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-02-28")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-03-15")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-03-31")); s.setStartDate(QDate(2007, 1, 31)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-02-15")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-02-28")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-03-15")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-03-31")); s.setStartDate(QDate(2007, 4, 29)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-05-14")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-05-29")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-06-14")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-06-29")); s.setStartDate(QDate(2007, 4, 30)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-05-15")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-05-31")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-06-15")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-06-30")); } void MyMoneyScheduleTest::testPaymentDates() { MyMoneySchedule sch; QString ref_ok = QString( "\n" "\n" "\n" // krazy:exclude=spelling " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" "\n" ); QDomDocument doc; QDomElement node; doc.setContent(ref_ok); node = doc.documentElement().firstChild().toElement(); QDate startDate(2006, 1, 28); QDate endDate(2006, 5, 30); try { sch = MyMoneySchedule(node); QDate nextPayment = sch.nextPayment(startDate); QList list = sch.paymentDates(nextPayment, endDate); QCOMPARE(list.count(), 3); QCOMPARE(list[0], QDate(2006, 2, 28)); QCOMPARE(list[1], QDate(2006, 3, 31)); // Would fall on a Sunday so gets moved back to 28th. QCOMPARE(list[2], QDate(2006, 4, 28)); // Add tests for each possible occurrence. // Check how paymentDates is meant to work // Build a list of expected dates and compare // Schedule::Occurrence::Once sch.setOccurrence(Schedule::Occurrence::Once); startDate.setDate(2009, 1, 1); endDate.setDate(2009, 12, 31); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 1); QCOMPARE(list[0], QDate(2009, 1, 1)); // Schedule::Occurrence::Daily sch.setOccurrence(Schedule::Occurrence::Daily); startDate.setDate(2009, 1, 1); endDate.setDate(2009, 1, 5); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2009, 1, 1)); QCOMPARE(list[1], QDate(2009, 1, 2)); // Would fall on Saturday so gets moved to 2nd. QCOMPARE(list[2], QDate(2009, 1, 2)); // Would fall on Sunday so gets moved to 2nd. QCOMPARE(list[3], QDate(2009, 1, 2)); QCOMPARE(list[4], QDate(2009, 1, 5)); // Schedule::Occurrence::Daily with multiplier 2 sch.setOccurrenceMultiplier(2); list = sch.paymentDates(startDate.addDays(1), endDate); QCOMPARE(list.count(), 2); // Would fall on Sunday so gets moved to 2nd. QCOMPARE(list[0], QDate(2009, 1, 2)); QCOMPARE(list[1], QDate(2009, 1, 5)); sch.setOccurrenceMultiplier(1); // Schedule::Occurrence::Weekly sch.setOccurrence(Schedule::Occurrence::Weekly); startDate.setDate(2009, 1, 6); endDate.setDate(2009, 2, 4); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2009, 1, 6)); QCOMPARE(list[1], QDate(2009, 1, 13)); QCOMPARE(list[2], QDate(2009, 1, 20)); QCOMPARE(list[3], QDate(2009, 1, 27)); QCOMPARE(list[4], QDate(2009, 2, 3)); // Schedule::Occurrence::EveryOtherWeek sch.setOccurrence(Schedule::Occurrence::EveryOtherWeek); startDate.setDate(2009, 2, 5); endDate.setDate(2009, 4, 3); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2009, 2, 5)); QCOMPARE(list[1], QDate(2009, 2, 19)); QCOMPARE(list[2], QDate(2009, 3, 5)); QCOMPARE(list[3], QDate(2009, 3, 19)); QCOMPARE(list[4], QDate(2009, 4, 2)); // Schedule::Occurrence::Fortnightly sch.setOccurrence(Schedule::Occurrence::Fortnightly); startDate.setDate(2009, 4, 4); endDate.setDate(2009, 5, 31); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 4); // First one would fall on a Saturday and would get moved // to 3rd which is before start date so is not in list. // Would fall on a Saturday so gets moved to 17th. QCOMPARE(list[0], QDate(2009, 4, 17)); // Would fall on a Saturday so gets moved to 1st. QCOMPARE(list[1], QDate(2009, 5, 1)); // Would fall on a Saturday so gets moved to 15th. QCOMPARE(list[2], QDate(2009, 5, 15)); // Would fall on a Saturday so gets moved to 29th. QCOMPARE(list[3], QDate(2009, 5, 29)); // Schedule::Occurrence::EveryHalfMonth sch.setOccurrence(Schedule::Occurrence::EveryHalfMonth); startDate.setDate(2009, 6, 1); endDate.setDate(2009, 8, 11); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2009, 6, 1)); QCOMPARE(list[1], QDate(2009, 6, 16)); QCOMPARE(list[2], QDate(2009, 7, 1)); QCOMPARE(list[3], QDate(2009, 7, 16)); // Would fall on a Saturday so gets moved to 31st. QCOMPARE(list[4], QDate(2009, 7, 31)); // Schedule::Occurrence::EveryThreeWeeks sch.setOccurrence(Schedule::Occurrence::EveryThreeWeeks); startDate.setDate(2009, 8, 12); endDate.setDate(2009, 11, 12); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2009, 8, 12)); QCOMPARE(list[1], QDate(2009, 9, 2)); QCOMPARE(list[2], QDate(2009, 9, 23)); QCOMPARE(list[3], QDate(2009, 10, 14)); QCOMPARE(list[4], QDate(2009, 11, 4)); // Schedule::Occurrence::EveryFourWeeks sch.setOccurrence(Schedule::Occurrence::EveryFourWeeks); startDate.setDate(2009, 11, 13); endDate.setDate(2010, 3, 13); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2009, 11, 13)); QCOMPARE(list[1], QDate(2009, 12, 11)); QCOMPARE(list[2], QDate(2010, 1, 8)); QCOMPARE(list[3], QDate(2010, 2, 5)); QCOMPARE(list[4], QDate(2010, 3, 5)); // Schedule::Occurrence::EveryThirtyDays sch.setOccurrence(Schedule::Occurrence::EveryThirtyDays); startDate.setDate(2010, 3, 19); endDate.setDate(2010, 7, 19); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2010, 3, 19)); // Would fall on a Sunday so gets moved to 16th. QCOMPARE(list[1], QDate(2010, 4, 16)); QCOMPARE(list[2], QDate(2010, 5, 18)); QCOMPARE(list[3], QDate(2010, 6, 17)); // Would fall on a Saturday so gets moved to 16th. QCOMPARE(list[4], QDate(2010, 7, 16)); // Schedule::Occurrence::EveryEightWeeks sch.setOccurrence(Schedule::Occurrence::EveryEightWeeks); startDate.setDate(2010, 7, 26); endDate.setDate(2011, 3, 26); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2010, 7, 26)); QCOMPARE(list[1], QDate(2010, 9, 20)); QCOMPARE(list[2], QDate(2010, 11, 15)); QCOMPARE(list[3], QDate(2011, 1, 10)); QCOMPARE(list[4], QDate(2011, 3, 7)); // Schedule::Occurrence::EveryOtherMonth sch.setOccurrence(Schedule::Occurrence::EveryOtherMonth); startDate.setDate(2011, 3, 14); endDate.setDate(2011, 11, 20); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2011, 3, 14)); // Would fall on a Saturday so gets moved to 13th. QCOMPARE(list[1], QDate(2011, 5, 13)); QCOMPARE(list[2], QDate(2011, 7, 14)); QCOMPARE(list[3], QDate(2011, 9, 14)); QCOMPARE(list[4], QDate(2011, 11, 14)); // Schedule::Occurrence::EveryThreeMonths sch.setOccurrence(Schedule::Occurrence::EveryThreeMonths); startDate.setDate(2011, 11, 15); endDate.setDate(2012, 11, 19); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2011, 11, 15)); QCOMPARE(list[1], QDate(2012, 2, 15)); QCOMPARE(list[2], QDate(2012, 5, 15)); QCOMPARE(list[3], QDate(2012, 8, 15)); QCOMPARE(list[4], QDate(2012, 11, 15)); // Schedule::Occurrence::Quarterly sch.setOccurrence(Schedule::Occurrence::Quarterly); startDate.setDate(2012, 11, 20); endDate.setDate(2013, 11, 23); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2012, 11, 20)); QCOMPARE(list[1], QDate(2013, 2, 20)); QCOMPARE(list[2], QDate(2013, 5, 20)); QCOMPARE(list[3], QDate(2013, 8, 20)); QCOMPARE(list[4], QDate(2013, 11, 20)); // Schedule::Occurrence::EveryFourMonths sch.setOccurrence(Schedule::Occurrence::EveryFourMonths); startDate.setDate(2013, 11, 21); endDate.setDate(2015, 3, 23); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2013, 11, 21)); QCOMPARE(list[1], QDate(2014, 3, 21)); QCOMPARE(list[2], QDate(2014, 7, 21)); QCOMPARE(list[3], QDate(2014, 11, 21)); // Would fall on a Saturday so gets moved to 20th. QCOMPARE(list[4], QDate(2015, 3, 20)); // Schedule::Occurrence::TwiceYearly sch.setOccurrence(Schedule::Occurrence::TwiceYearly); startDate.setDate(2015, 3, 22); endDate.setDate(2017, 3, 29); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 4); // First date would fall on a Sunday which would get moved // to 20th which is before start date so not in list. QCOMPARE(list[0], QDate(2015, 9, 22)); QCOMPARE(list[1], QDate(2016, 3, 22)); QCOMPARE(list[2], QDate(2016, 9, 22)); QCOMPARE(list[3], QDate(2017, 3, 22)); // Schedule::Occurrence::Yearly sch.setOccurrence(Schedule::Occurrence::Yearly); startDate.setDate(2017, 3, 23); endDate.setDate(2021, 3, 29); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2017, 3, 23)); QCOMPARE(list[1], QDate(2018, 3, 23)); // Would fall on a Saturday so gets moved to 22nd. QCOMPARE(list[2], QDate(2019, 3, 22)); QCOMPARE(list[3], QDate(2020, 3, 23)); QCOMPARE(list[4], QDate(2021, 3, 23)); // Schedule::Occurrence::EveryOtherYear sch.setOccurrence(Schedule::Occurrence::EveryOtherYear); startDate.setDate(2021, 3, 24); endDate.setDate(2029, 3, 30); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2021, 3, 24)); QCOMPARE(list[1], QDate(2023, 3, 24)); QCOMPARE(list[2], QDate(2025, 3, 24)); QCOMPARE(list[3], QDate(2027, 3, 24)); // Would fall on a Saturday so gets moved to 23rd. QCOMPARE(list[4], QDate(2029, 3, 23)); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyScheduleTest::testWriteXML() { MyMoneySchedule sch("A Name", Schedule::Type::Bill, Schedule::Occurrence::Weekly, 123, Schedule::PaymentType::DirectDebit, QDate::currentDate(), QDate(), true, true); sch.setLastPayment(QDate::currentDate()); sch.recordPayment(QDate::currentDate()); - sch.setId("SCH0001"); + sch.d_func()->setId("SCH0001"); MyMoneyTransaction t; t.setPostDate(QDate(2001, 12, 28)); t.setEntryDate(QDate(2003, 9, 29)); - t.setId("T000000000000000001"); + t.d_func()->setId("T000000000000000001"); t.setMemo("Wohnung:Miete"); t.setCommodity("EUR"); t.setValue("key", "value"); MyMoneySplit s; s.setPayeeId("P000001"); s.setShares(MyMoneyMoney(96379, 100)); s.setValue(MyMoneyMoney(96379, 100)); s.setAccountId("A000076"); s.setBankID("SPID1"); s.setReconcileFlag(eMyMoney::Split::State::Reconciled); t.addSplit(s); s.setPayeeId("P000001"); s.setShares(MyMoneyMoney(-96379, 100)); s.setValue(MyMoneyMoney(-96379, 100)); s.setAccountId("A000276"); s.setBankID("SPID2"); s.setReconcileFlag(eMyMoney::Split::State::Cleared); s.clearId(); t.addSplit(s); sch.setTransaction(t); QDomDocument doc("TEST"); QDomElement el = doc.createElement("SCHEDULE-CONTAINER"); doc.appendChild(el); sch.writeXML(doc, el); QCOMPARE(doc.doctype().name(), QLatin1String("TEST")); QDomElement scheduleContainer = doc.documentElement(); QVERIFY(scheduleContainer.isElement()); QCOMPARE(scheduleContainer.tagName(), QLatin1String("SCHEDULE-CONTAINER")); QCOMPARE(scheduleContainer.childNodes().size(), 1); QVERIFY(scheduleContainer.childNodes().at(0).isElement()); QDomElement schedule = scheduleContainer.childNodes().at(0).toElement(); QCOMPARE(schedule.tagName(), QLatin1String("SCHEDULED_TX")); QCOMPARE(schedule.attribute("id"), QLatin1String("SCH0001")); QCOMPARE(schedule.attribute("paymentType"), QLatin1String("1")); QCOMPARE(schedule.attribute("autoEnter"), QLatin1String("1")); QCOMPARE(schedule.attribute("occurenceMultiplier"), QLatin1String("123")); // krazy:exclude=spelling QCOMPARE(schedule.attribute("startDate"), QDate::currentDate().toString(Qt::ISODate)); QCOMPARE(schedule.attribute("lastPayment"), QDate::currentDate().toString(Qt::ISODate)); QCOMPARE(schedule.attribute("occurenceMultiplier"), QLatin1String("123")); // krazy:exclude=spelling QCOMPARE(schedule.attribute("occurence"), QLatin1String("4")); // krazy:exclude=spelling QCOMPARE(schedule.attribute("type"), QLatin1String("1")); QCOMPARE(schedule.attribute("name"), QLatin1String("A Name")); QCOMPARE(schedule.attribute("fixed"), QLatin1String("1")); QCOMPARE(schedule.attribute("endDate"), QString()); QCOMPARE(schedule.childNodes().size(), 2); QVERIFY(schedule.childNodes().at(0).isElement()); QDomElement payments = schedule.childNodes().at(0).toElement(); QVERIFY(schedule.childNodes().at(1).isElement()); QDomElement transaction = schedule.childNodes().at(1).toElement(); QCOMPARE(transaction.tagName(), QLatin1String("TRANSACTION")); QCOMPARE(transaction.attribute("id"), QString()); QCOMPARE(transaction.attribute("postdate"), QLatin1String("2001-12-28")); QCOMPARE(transaction.attribute("commodity"), QLatin1String("EUR")); QCOMPARE(transaction.attribute("memo"), QLatin1String("Wohnung:Miete")); QCOMPARE(transaction.attribute("entrydate"), QLatin1String("2003-09-29")); QCOMPARE(transaction.childNodes().size(), 2); QVERIFY(transaction.childNodes().at(0).isElement()); QDomElement splits = transaction.childNodes().at(0).toElement(); QCOMPARE(splits.tagName(), QLatin1String("SPLITS")); QCOMPARE(splits.childNodes().size(), 2); QVERIFY(splits.childNodes().at(0).isElement()); QDomElement split1 = splits.childNodes().at(0).toElement(); QCOMPARE(split1.tagName(), QLatin1String("SPLIT")); QCOMPARE(split1.attribute("id"), QLatin1String("S0001")); QCOMPARE(split1.attribute("payee"), QLatin1String("P000001")); QCOMPARE(split1.attribute("reconcileflag"), QLatin1String("2")); QCOMPARE(split1.attribute("shares"), QLatin1String("96379/100")); QCOMPARE(split1.attribute("reconciledate"), QString()); QCOMPARE(split1.attribute("action"), QString()); QCOMPARE(split1.attribute("bankid"), QString()); QCOMPARE(split1.attribute("account"), QLatin1String("A000076")); QCOMPARE(split1.attribute("number"), QString()); QCOMPARE(split1.attribute("value"), QLatin1String("96379/100")); QCOMPARE(split1.attribute("memo"), QString()); QCOMPARE(split1.childNodes().size(), 0); QVERIFY(splits.childNodes().at(1).isElement()); QDomElement split2 = splits.childNodes().at(1).toElement(); QCOMPARE(split2.tagName(), QLatin1String("SPLIT")); QCOMPARE(split2.attribute("id"), QLatin1String("S0002")); QCOMPARE(split2.attribute("payee"), QLatin1String("P000001")); QCOMPARE(split2.attribute("reconcileflag"), QLatin1String("1")); QCOMPARE(split2.attribute("shares"), QLatin1String("-96379/100")); QCOMPARE(split2.attribute("reconciledate"), QString()); QCOMPARE(split2.attribute("action"), QString()); QCOMPARE(split2.attribute("bankid"), QString()); QCOMPARE(split2.attribute("account"), QLatin1String("A000276")); QCOMPARE(split2.attribute("number"), QString()); QCOMPARE(split2.attribute("value"), QLatin1String("-96379/100")); QCOMPARE(split2.attribute("memo"), QString()); QCOMPARE(split2.childNodes().size(), 0); QDomElement keyValuePairs = transaction.childNodes().at(1).toElement(); QCOMPARE(keyValuePairs.tagName(), QLatin1String("KEYVALUEPAIRS")); QCOMPARE(keyValuePairs.childNodes().size(), 1); QVERIFY(keyValuePairs.childNodes().at(0).isElement()); QDomElement keyValuePair1 = keyValuePairs.childNodes().at(0).toElement(); QCOMPARE(keyValuePair1.tagName(), QLatin1String("PAIR")); QCOMPARE(keyValuePair1.attribute("key"), QLatin1String("key")); QCOMPARE(keyValuePair1.attribute("value"), QLatin1String("value")); QCOMPARE(keyValuePair1.childNodes().size(), 0); } void MyMoneyScheduleTest::testReadXML() { MyMoneySchedule sch; QString ref_ok1 = QString( "\n" "\n" " \n" // krazy:exclude=spelling " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" ).arg(QDate::currentDate().toString(Qt::ISODate)) .arg(QDate::currentDate().toString(Qt::ISODate)) .arg(QDate::currentDate().toString(Qt::ISODate)); // diff to ref_ok1 is that we now have an empty entrydate // in the transaction parameters QString ref_ok2 = QString( "\n" "\n" " \n" // krazy:exclude=spelling " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" ).arg(QDate::currentDate().toString(Qt::ISODate)) .arg(QDate::currentDate().toString(Qt::ISODate)) .arg(QDate::currentDate().toString(Qt::ISODate)); QString ref_false = QString( "\n" "\n" " \n" // krazy:exclude=spelling " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" ).arg(QDate::currentDate().toString(Qt::ISODate)) .arg(QDate::currentDate().toString(Qt::ISODate)) .arg(QDate::currentDate().toString(Qt::ISODate)); QDomDocument doc; QDomElement node; doc.setContent(ref_false); node = doc.documentElement().firstChild().toElement(); try { sch = MyMoneySchedule(node); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { } doc.setContent(ref_ok1); node = doc.documentElement().firstChild().toElement(); try { sch = MyMoneySchedule(node); QCOMPARE(sch.id(), QLatin1String("SCH0002")); QCOMPARE(sch.nextDueDate(), QDate::currentDate().addDays(7)); QCOMPARE(sch.startDate(), QDate::currentDate()); QCOMPARE(sch.endDate(), QDate()); QCOMPARE(sch.autoEnter(), true); QCOMPARE(sch.isFixed(), true); QCOMPARE(sch.weekendOption(), Schedule::WeekendOption::MoveNothing); QCOMPARE(sch.lastPayment(), QDate::currentDate()); QCOMPARE(sch.paymentType(), Schedule::PaymentType::DirectDebit); QCOMPARE(sch.type(), Schedule::Type::Bill); QCOMPARE(sch.name(), QLatin1String("A Name")); QCOMPARE(sch.occurrence(), Schedule::Occurrence::Weekly); QCOMPARE(sch.occurrenceMultiplier(), 1); QCOMPARE(sch.nextDueDate(), sch.lastPayment().addDays(7)); QCOMPARE(sch.recordedPayments().count(), 1); QCOMPARE(sch.recordedPayments()[0], QDate::currentDate()); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } doc.setContent(ref_ok2); node = doc.documentElement().firstChild().toElement(); try { sch = MyMoneySchedule(node); QCOMPARE(sch.id(), QLatin1String("SCH0002")); QCOMPARE(sch.nextDueDate(), QDate::currentDate().addDays(7)); QCOMPARE(sch.startDate(), QDate::currentDate()); QCOMPARE(sch.endDate(), QDate()); QCOMPARE(sch.autoEnter(), true); QCOMPARE(sch.isFixed(), true); QCOMPARE(sch.weekendOption(), Schedule::WeekendOption::MoveNothing); QCOMPARE(sch.lastPayment(), QDate::currentDate()); QCOMPARE(sch.paymentType(), Schedule::PaymentType::DirectDebit); QCOMPARE(sch.type(), Schedule::Type::Bill); QCOMPARE(sch.name(), QLatin1String("A Name")); QCOMPARE(sch.occurrence(), Schedule::Occurrence::Weekly); QCOMPARE(sch.occurrenceMultiplier(), 1); QCOMPARE(sch.nextDueDate(), sch.lastPayment().addDays(7)); QCOMPARE(sch.recordedPayments().count(), 1); QCOMPARE(sch.recordedPayments()[0], QDate::currentDate()); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyScheduleTest::testHasReferenceTo() { MyMoneySchedule sch; QString ref_ok = QString( "\n" "\n" " \n" // krazy:exclude=spelling " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" ).arg(QDate::currentDate().toString(Qt::ISODate)) .arg(QDate::currentDate().toString(Qt::ISODate)) .arg(QDate::currentDate().toString(Qt::ISODate)); QDomDocument doc; QDomElement node; doc.setContent(ref_ok); node = doc.documentElement().firstChild().toElement(); try { sch = MyMoneySchedule(node); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } QCOMPARE(sch.hasReferenceTo(QLatin1String("P000001")), true); QCOMPARE(sch.hasReferenceTo(QLatin1String("A000276")), true); QCOMPARE(sch.hasReferenceTo(QLatin1String("A000076")), true); QCOMPARE(sch.hasReferenceTo(QLatin1String("EUR")), true); } void MyMoneyScheduleTest::testAdjustedNextDueDate() { MyMoneySchedule s; QDate dueDate(2007, 9, 3); // start on a Monday for (int i = 0; i < 7; ++i) { s.setNextDueDate(dueDate); s.setWeekendOption(Schedule::WeekendOption::MoveNothing); QCOMPARE(s.adjustedNextDueDate(), dueDate); s.setWeekendOption(Schedule::WeekendOption::MoveBefore); switch (i) { case 5: // Saturday case 6: // Sunday QCOMPARE(s.adjustedNextDueDate(), QDate(2007, 9, 7)); break; default: QCOMPARE(s.adjustedNextDueDate(), dueDate); break; } s.setWeekendOption(Schedule::WeekendOption::MoveAfter); switch (i) { case 5: // Saturday case 6: // Sunday QCOMPARE(s.adjustedNextDueDate(), QDate(2007, 9, 10)); break; default: QCOMPARE(s.adjustedNextDueDate(), dueDate); break; } dueDate = dueDate.addDays(1); } } void MyMoneyScheduleTest::testModifyNextDueDate() { MyMoneySchedule s; s.setStartDate(QDate(2007, 1, 2)); s.setOccurrence(Schedule::Occurrence::Monthly); s.setNextDueDate(s.startDate().addMonths(1)); s.setLastPayment(s.startDate()); QList dates; dates = s.paymentDates(QDate(2007, 2, 2), QDate(2007, 2, 2)); QCOMPARE(s.nextDueDate(), QDate(2007, 2, 2)); QCOMPARE(dates.count(), 1); QCOMPARE(dates[0], QDate(2007, 2, 2)); s.setNextDueDate(QDate(2007, 1, 24)); dates = s.paymentDates(QDate(2007, 2, 1), QDate(2007, 2, 1)); QCOMPARE(s.nextDueDate(), QDate(2007, 1, 24)); QCOMPARE(dates.count(), 0); dates = s.paymentDates(QDate(2007, 1, 24), QDate(2007, 1, 24)); QCOMPARE(dates.count(), 1); dates = s.paymentDates(QDate(2007, 1, 24), QDate(2007, 2, 24)); QCOMPARE(dates.count(), 2); QCOMPARE(dates[0], QDate(2007, 1, 24)); QCOMPARE(dates[1], QDate(2007, 2, 2)); } void MyMoneyScheduleTest::testDaysBetweenEvents() { QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::Once), 0); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::Daily), 1); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::Weekly), 7); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::EveryOtherWeek), 14); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::Fortnightly), 14); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::EveryHalfMonth), 15); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::EveryThreeWeeks), 21); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::EveryFourWeeks), 28); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::EveryThirtyDays), 30); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::Monthly), 30); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::EveryEightWeeks), 56); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::EveryOtherMonth), 60); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::EveryThreeMonths), 90); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::Quarterly), 90); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::EveryFourMonths), 120); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::TwiceYearly), 180); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::Yearly), 360); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::EveryOtherYear), 0); } void MyMoneyScheduleTest::testEventsPerYear() { QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::Once), 0); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::Daily), 365); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::Weekly), 52); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::EveryOtherWeek), 26); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::Fortnightly), 26); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::EveryHalfMonth), 24); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::EveryThreeWeeks), 17); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::EveryFourWeeks), 13); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::EveryThirtyDays), 12); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::Monthly), 12); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::EveryEightWeeks), 6); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::EveryOtherMonth), 6); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::EveryThreeMonths), 4); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::Quarterly), 4); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::EveryFourMonths), 3); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::TwiceYearly), 2); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::Yearly), 1); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::EveryOtherYear), 0); } void MyMoneyScheduleTest::testOccurrenceToString() { // For each occurrenceE test MyMoneySchedule::occurrenceToString(occurrenceE) QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Once), QLatin1String("Once")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Daily), QLatin1String("Daily")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Weekly), QLatin1String("Weekly")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryOtherWeek), QLatin1String("Every other week")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Fortnightly), QLatin1String("Fortnightly")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryHalfMonth), QLatin1String("Every half month")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryThreeWeeks), QLatin1String("Every three weeks")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryFourWeeks), QLatin1String("Every four weeks")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryThirtyDays), QLatin1String("Every thirty days")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Monthly), QLatin1String("Monthly")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryEightWeeks), QLatin1String("Every eight weeks")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryOtherMonth), QLatin1String("Every two months")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryThreeMonths), QLatin1String("Every three months")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Quarterly), QLatin1String("Quarterly")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryFourMonths), QLatin1String("Every four months")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::TwiceYearly), QLatin1String("Twice yearly")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Yearly), QLatin1String("Yearly")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryOtherYear), QLatin1String("Every other year")); // For each occurrenceE set occurrence and compare occurrenceToString() with oTS(occurrence()) MyMoneySchedule s; s.setStartDate(QDate(2007, 1, 1)); s.setNextDueDate(s.startDate()); s.setLastPayment(s.startDate()); s.setOccurrence(Schedule::Occurrence::Once); QCOMPARE(s.occurrenceToString(), QLatin1String("Once")); s.setOccurrence(Schedule::Occurrence::Daily); QCOMPARE(s.occurrenceToString(), QLatin1String("Daily")); s.setOccurrence(Schedule::Occurrence::Weekly); QCOMPARE(s.occurrenceToString(), QLatin1String("Weekly")); s.setOccurrence(Schedule::Occurrence::EveryOtherWeek); QCOMPARE(s.occurrenceToString(), QLatin1String("Every other week")); // Fortnightly no longer used: Every other week used instead s.setOccurrence(Schedule::Occurrence::Fortnightly); QCOMPARE(s.occurrenceToString(), QLatin1String("Every other week")); s.setOccurrence(Schedule::Occurrence::EveryHalfMonth); QCOMPARE(s.occurrenceToString(), QLatin1String("Every half month")); s.setOccurrence(Schedule::Occurrence::EveryThreeWeeks); QCOMPARE(s.occurrenceToString(), QLatin1String("Every three weeks")); s.setOccurrence(Schedule::Occurrence::EveryFourWeeks); QCOMPARE(s.occurrenceToString(), QLatin1String("Every four weeks")); s.setOccurrence(Schedule::Occurrence::EveryThirtyDays); QCOMPARE(s.occurrenceToString(), QLatin1String("Every thirty days")); s.setOccurrence(Schedule::Occurrence::Monthly); QCOMPARE(s.occurrenceToString(), QLatin1String("Monthly")); s.setOccurrence(Schedule::Occurrence::EveryEightWeeks); QCOMPARE(s.occurrenceToString(), QLatin1String("Every eight weeks")); s.setOccurrence(Schedule::Occurrence::EveryOtherMonth); QCOMPARE(s.occurrenceToString(), QLatin1String("Every two months")); s.setOccurrence(Schedule::Occurrence::EveryThreeMonths); QCOMPARE(s.occurrenceToString(), QLatin1String("Every three months")); // Quarterly no longer used. Every three months used instead s.setOccurrence(Schedule::Occurrence::Quarterly); QCOMPARE(s.occurrenceToString(), QLatin1String("Every three months")); s.setOccurrence(Schedule::Occurrence::EveryFourMonths); QCOMPARE(s.occurrenceToString(), QLatin1String("Every four months")); s.setOccurrence(Schedule::Occurrence::TwiceYearly); QCOMPARE(s.occurrenceToString(), QLatin1String("Twice yearly")); s.setOccurrence(Schedule::Occurrence::Yearly); QCOMPARE(s.occurrenceToString(), QLatin1String("Yearly")); s.setOccurrence(Schedule::Occurrence::EveryOtherYear); QCOMPARE(s.occurrenceToString(), QLatin1String("Every other year")); // Test occurrenceToString(mult,occ) // Test all pairs equivalent to simple occurrences: should return the same as occurrenceToString(simpleOcc) // TODO replace string with (mult,occ) call. QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Once), MyMoneySchedule::occurrenceToString(1, Schedule::Occurrence::Once)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Daily), MyMoneySchedule::occurrenceToString(1, Schedule::Occurrence::Daily)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Weekly), MyMoneySchedule::occurrenceToString(1, Schedule::Occurrence::Weekly)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryOtherWeek), MyMoneySchedule::occurrenceToString(2, Schedule::Occurrence::Weekly)); // Fortnightly will no longer be used: only Every Other Week QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryHalfMonth), MyMoneySchedule::occurrenceToString(1, Schedule::Occurrence::EveryHalfMonth)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryThreeWeeks), MyMoneySchedule::occurrenceToString(3, Schedule::Occurrence::Weekly)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryFourWeeks), MyMoneySchedule::occurrenceToString(4, Schedule::Occurrence::Weekly)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Monthly), MyMoneySchedule::occurrenceToString(1, Schedule::Occurrence::Monthly)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryEightWeeks), MyMoneySchedule::occurrenceToString(8, Schedule::Occurrence::Weekly)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryOtherMonth), MyMoneySchedule::occurrenceToString(2, Schedule::Occurrence::Monthly)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryThreeMonths), MyMoneySchedule::occurrenceToString(3, Schedule::Occurrence::Monthly)); // Quarterly will no longer be used: only Every Three Months QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryFourMonths), MyMoneySchedule::occurrenceToString(4, Schedule::Occurrence::Monthly)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::TwiceYearly), MyMoneySchedule::occurrenceToString(6, Schedule::Occurrence::Monthly)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Yearly), MyMoneySchedule::occurrenceToString(1, Schedule::Occurrence::Yearly)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryOtherYear), MyMoneySchedule::occurrenceToString(2, Schedule::Occurrence::Yearly)); // Test additional calls with other mult,occ QCOMPARE(MyMoneySchedule::occurrenceToString(2, Schedule::Occurrence::Once), QLatin1String("2 times")); QCOMPARE(MyMoneySchedule::occurrenceToString(2, Schedule::Occurrence::Daily), QLatin1String("Every 2 days")); QCOMPARE(MyMoneySchedule::occurrenceToString(5, Schedule::Occurrence::Weekly), QLatin1String("Every 5 weeks")); QCOMPARE(MyMoneySchedule::occurrenceToString(2, Schedule::Occurrence::EveryHalfMonth), QLatin1String("Every 2 half months")); QCOMPARE(MyMoneySchedule::occurrenceToString(5, Schedule::Occurrence::Monthly), QLatin1String("Every 5 months")); QCOMPARE(MyMoneySchedule::occurrenceToString(3, Schedule::Occurrence::Yearly), QLatin1String("Every 3 years")); QCOMPARE(MyMoneySchedule::occurrenceToString(37, Schedule::Occurrence::Once), QLatin1String("37 times")); QCOMPARE(MyMoneySchedule::occurrenceToString(43, Schedule::Occurrence::Daily), QLatin1String("Every 43 days")); QCOMPARE(MyMoneySchedule::occurrenceToString(61, Schedule::Occurrence::Weekly), QLatin1String("Every 61 weeks")); QCOMPARE(MyMoneySchedule::occurrenceToString(73, Schedule::Occurrence::EveryHalfMonth), QLatin1String("Every 73 half months")); QCOMPARE(MyMoneySchedule::occurrenceToString(83, Schedule::Occurrence::Monthly), QLatin1String("Every 83 months")); QCOMPARE(MyMoneySchedule::occurrenceToString(89, Schedule::Occurrence::Yearly), QLatin1String("Every 89 years")); // Test instance-level occurrenceToString method is using occurrencePeriod and multiplier // For each base occurrence set occurrencePeriod and multiplier s.setOccurrencePeriod(Schedule::Occurrence::Once); s.setOccurrenceMultiplier(1); s.setOccurrence(Schedule::Occurrence::Once); s.setOccurrenceMultiplier(1); QCOMPARE(s.occurrenceToString(), QLatin1String("Once")); s.setOccurrenceMultiplier(2); QCOMPARE(s.occurrenceToString(), QLatin1String("2 times")); s.setOccurrenceMultiplier(3); QCOMPARE(s.occurrenceToString(), QLatin1String("3 times")); s.setOccurrencePeriod(Schedule::Occurrence::Daily); s.setOccurrenceMultiplier(1); QCOMPARE(s.occurrenceToString(), QLatin1String("Daily")); s.setOccurrenceMultiplier(30); QCOMPARE(s.occurrenceToString(), QLatin1String("Every thirty days")); s.setOccurrenceMultiplier(3); QCOMPARE(s.occurrenceToString(), QLatin1String("Every 3 days")); s.setOccurrence(Schedule::Occurrence::Weekly); QCOMPARE(s.occurrenceToString(), QLatin1String("Weekly")); s.setOccurrenceMultiplier(2); QCOMPARE(s.occurrenceToString(), QLatin1String("Every other week")); s.setOccurrenceMultiplier(3); QCOMPARE(s.occurrenceToString(), QLatin1String("Every three weeks")); s.setOccurrenceMultiplier(4); QCOMPARE(s.occurrenceToString(), QLatin1String("Every four weeks")); s.setOccurrenceMultiplier(5); QCOMPARE(s.occurrenceToString(), QLatin1String("Every 5 weeks")); s.setOccurrenceMultiplier(7); QCOMPARE(s.occurrenceToString(), QLatin1String("Every 7 weeks")); s.setOccurrenceMultiplier(8); QCOMPARE(s.occurrenceToString(), QLatin1String("Every eight weeks")); s.setOccurrenceMultiplier(9); QCOMPARE(s.occurrenceToString(), QLatin1String("Every 9 weeks")); s.setOccurrence(Schedule::Occurrence::EveryHalfMonth); s.setOccurrenceMultiplier(1); QCOMPARE(s.occurrenceToString(), QLatin1String("Every half month")); s.setOccurrenceMultiplier(2); QCOMPARE(s.occurrenceToString(), QLatin1String("Every 2 half months")); s.setOccurrence(Schedule::Occurrence::Monthly); s.setOccurrenceMultiplier(1); QCOMPARE(s.occurrenceToString(), QLatin1String("Monthly")); s.setOccurrenceMultiplier(2); QCOMPARE(s.occurrenceToString(), QLatin1String("Every two months")); s.setOccurrenceMultiplier(3); QCOMPARE(s.occurrenceToString(), QLatin1String("Every three months")); s.setOccurrenceMultiplier(4); QCOMPARE(s.occurrenceToString(), QLatin1String("Every four months")); s.setOccurrenceMultiplier(5); QCOMPARE(s.occurrenceToString(), QLatin1String("Every 5 months")); s.setOccurrenceMultiplier(6); QCOMPARE(s.occurrenceToString(), QLatin1String("Twice yearly")); s.setOccurrenceMultiplier(7); QCOMPARE(s.occurrenceToString(), QLatin1String("Every 7 months")); s.setOccurrence(Schedule::Occurrence::Yearly); s.setOccurrenceMultiplier(1); QCOMPARE(s.occurrenceToString(), QLatin1String("Yearly")); s.setOccurrenceMultiplier(2); QCOMPARE(s.occurrenceToString(), QLatin1String("Every other year")); s.setOccurrenceMultiplier(3); QCOMPARE(s.occurrenceToString(), QLatin1String("Every 3 years")); } void MyMoneyScheduleTest::testOccurrencePeriodToString() { // For each occurrenceE test MyMoneySchedule::occurrencePeriodToString(occurrenceE) // Base occurrences are translated QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::Once), QLatin1String("Once")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::Daily), QLatin1String("Day")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::Weekly), QLatin1String("Week")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::EveryHalfMonth), QLatin1String("Half-month")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::Monthly), QLatin1String("Month")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::Yearly), QLatin1String("Year")); // All others are not translated so return Any QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::EveryOtherWeek), QLatin1String("Any")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::Fortnightly), QLatin1String("Any")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::EveryThreeWeeks), QLatin1String("Any")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::EveryFourWeeks), QLatin1String("Any")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::EveryThirtyDays), QLatin1String("Any")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::EveryEightWeeks), QLatin1String("Any")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::EveryOtherMonth), QLatin1String("Any")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::EveryThreeMonths), QLatin1String("Any")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::Quarterly), QLatin1String("Any")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::EveryFourMonths), QLatin1String("Any")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::TwiceYearly), QLatin1String("Any")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::EveryOtherYear), QLatin1String("Any")); } void MyMoneyScheduleTest::testOccurrencePeriod() { // Each occurrence: // Set occurrence using setOccurrencePeriod // occurrencePeriod should match what we set // occurrence depends on multiplier // TODO: // Once occurrence() and setOccurrence() are converting between compound and simple occurrences // we need to change the occurrence() check and add an occurrenceMultiplier() check MyMoneySchedule s; s.setStartDate(QDate(2007, 1, 1)); s.setNextDueDate(s.startDate()); s.setLastPayment(s.startDate()); // Set all base occurrences s.setOccurrencePeriod(Schedule::Occurrence::Once); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Once); s.setOccurrenceMultiplier(1); QCOMPARE(s.occurrenceMultiplier(), 1); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Once); QCOMPARE(s.occurrence(), Schedule::Occurrence::Once); s.setOccurrenceMultiplier(2); QCOMPARE(s.occurrenceMultiplier(), 2); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Once); QCOMPARE(s.occurrence(), Schedule::Occurrence::Once); s.setOccurrencePeriod(Schedule::Occurrence::Daily); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Daily); s.setOccurrenceMultiplier(1); QCOMPARE(s.occurrenceMultiplier(), 1); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Daily); QCOMPARE(s.occurrence(), Schedule::Occurrence::Daily); s.setOccurrenceMultiplier(30); QCOMPARE(s.occurrenceMultiplier(), 30); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Daily); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryThirtyDays); s.setOccurrenceMultiplier(2); QCOMPARE(s.occurrenceMultiplier(), 2); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Daily); QCOMPARE(s.occurrence(), Schedule::Occurrence::Daily); s.setOccurrencePeriod(Schedule::Occurrence::Weekly); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); s.setOccurrenceMultiplier(1); QCOMPARE(s.occurrenceMultiplier(), 1); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrence(), Schedule::Occurrence::Weekly); s.setOccurrenceMultiplier(2); QCOMPARE(s.occurrenceMultiplier(), 2); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryOtherWeek); s.setOccurrenceMultiplier(3); QCOMPARE(s.occurrenceMultiplier(), 3); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryThreeWeeks); s.setOccurrenceMultiplier(4); QCOMPARE(s.occurrenceMultiplier(), 4); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryFourWeeks); s.setOccurrenceMultiplier(5); QCOMPARE(s.occurrenceMultiplier(), 5); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrence(), Schedule::Occurrence::Weekly); s.setOccurrenceMultiplier(8); QCOMPARE(s.occurrenceMultiplier(), 8); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryEightWeeks); s.setOccurrencePeriod(Schedule::Occurrence::EveryHalfMonth); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::EveryHalfMonth); s.setOccurrenceMultiplier(1); QCOMPARE(s.occurrenceMultiplier(), 1); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::EveryHalfMonth); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryHalfMonth); s.setOccurrenceMultiplier(2); QCOMPARE(s.occurrenceMultiplier(), 2); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::EveryHalfMonth); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryHalfMonth); s.setOccurrencePeriod(Schedule::Occurrence::Monthly); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); s.setOccurrenceMultiplier(1); QCOMPARE(s.occurrenceMultiplier(), 1); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrence(), Schedule::Occurrence::Monthly); s.setOccurrenceMultiplier(2); QCOMPARE(s.occurrenceMultiplier(), 2); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryOtherMonth); s.setOccurrenceMultiplier(3); QCOMPARE(s.occurrenceMultiplier(), 3); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryThreeMonths); s.setOccurrenceMultiplier(4); QCOMPARE(s.occurrenceMultiplier(), 4); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryFourMonths); s.setOccurrenceMultiplier(5); QCOMPARE(s.occurrenceMultiplier(), 5); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrence(), Schedule::Occurrence::Monthly); s.setOccurrenceMultiplier(6); QCOMPARE(s.occurrenceMultiplier(), 6); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrence(), Schedule::Occurrence::TwiceYearly); s.setOccurrencePeriod(Schedule::Occurrence::Yearly); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Yearly); s.setOccurrenceMultiplier(1); QCOMPARE(s.occurrenceMultiplier(), 1); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Yearly); QCOMPARE(s.occurrence(), Schedule::Occurrence::Yearly); s.setOccurrenceMultiplier(2); QCOMPARE(s.occurrenceMultiplier(), 2); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Yearly); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryOtherYear); s.setOccurrenceMultiplier(3); QCOMPARE(s.occurrenceMultiplier(), 3); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Yearly); QCOMPARE(s.occurrence(), Schedule::Occurrence::Yearly); // Set occurrence: check occurrence, Period and Multiplier s.setOccurrence(Schedule::Occurrence::Once); QCOMPARE(s.occurrence(), Schedule::Occurrence::Once); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Once); QCOMPARE(s.occurrenceMultiplier(), 1); s.setOccurrence(Schedule::Occurrence::Daily); QCOMPARE(s.occurrence(), Schedule::Occurrence::Daily); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Daily); QCOMPARE(s.occurrenceMultiplier(), 1); s.setOccurrence(Schedule::Occurrence::EveryThirtyDays); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryThirtyDays); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Daily); QCOMPARE(s.occurrenceMultiplier(), 30); s.setOccurrence(Schedule::Occurrence::Weekly); QCOMPARE(s.occurrence(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrenceMultiplier(), 1); s.setOccurrence(Schedule::Occurrence::EveryOtherWeek); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryOtherWeek); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrenceMultiplier(), 2); // Fortnightly no longer used: Every other week used instead s.setOccurrence(Schedule::Occurrence::Fortnightly); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryOtherWeek); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrenceMultiplier(), 2); s.setOccurrence(Schedule::Occurrence::EveryThreeWeeks); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryThreeWeeks); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrenceMultiplier(), 3); s.setOccurrence(Schedule::Occurrence::EveryFourWeeks); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryFourWeeks); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrenceMultiplier(), 4); s.setOccurrence(Schedule::Occurrence::EveryEightWeeks); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryEightWeeks); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrenceMultiplier(), 8); s.setOccurrence(Schedule::Occurrence::EveryHalfMonth); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryHalfMonth); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::EveryHalfMonth); QCOMPARE(s.occurrenceMultiplier(), 1); s.setOccurrence(Schedule::Occurrence::Monthly); QCOMPARE(s.occurrence(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrenceMultiplier(), 1); s.setOccurrence(Schedule::Occurrence::EveryOtherMonth); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryOtherMonth); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrenceMultiplier(), 2); s.setOccurrence(Schedule::Occurrence::EveryThreeMonths); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryThreeMonths); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrenceMultiplier(), 3); // Quarterly no longer used. Every three months used instead s.setOccurrence(Schedule::Occurrence::Quarterly); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryThreeMonths); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrenceMultiplier(), 3); s.setOccurrence(Schedule::Occurrence::EveryFourMonths); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryFourMonths); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrenceMultiplier(), 4); s.setOccurrence(Schedule::Occurrence::TwiceYearly); QCOMPARE(s.occurrence(), Schedule::Occurrence::TwiceYearly); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrenceMultiplier(), 6); s.setOccurrence(Schedule::Occurrence::Yearly); QCOMPARE(s.occurrence(), Schedule::Occurrence::Yearly); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Yearly); QCOMPARE(s.occurrenceMultiplier(), 1); s.setOccurrence(Schedule::Occurrence::EveryOtherYear); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryOtherYear); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Yearly); QCOMPARE(s.occurrenceMultiplier(), 2); } void MyMoneyScheduleTest::testSimpleToFromCompoundOccurrence() { // Conversion between Simple and Compound occurrences // Each simple occurrence to compound occurrence Schedule::Occurrence occ; int mult; occ = Schedule::Occurrence::Once; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Once && mult == 1); occ = Schedule::Occurrence::Daily; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Daily && mult == 1); occ = Schedule::Occurrence::Weekly; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Weekly && mult == 1); occ = Schedule::Occurrence::EveryOtherWeek; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Weekly && mult == 2); occ = Schedule::Occurrence::Fortnightly; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Weekly && mult == 2); occ = Schedule::Occurrence::EveryHalfMonth; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::EveryHalfMonth && mult == 1); occ = Schedule::Occurrence::EveryThreeWeeks; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Weekly && mult == 3); occ = Schedule::Occurrence::EveryFourWeeks; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Weekly && mult == 4); occ = Schedule::Occurrence::EveryThirtyDays; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Daily && mult == 30); occ = Schedule::Occurrence::Monthly; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Monthly && mult == 1); occ = Schedule::Occurrence::EveryEightWeeks; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Weekly && mult == 8); occ = Schedule::Occurrence::EveryOtherMonth; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Monthly && mult == 2); occ = Schedule::Occurrence::EveryThreeMonths; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Monthly && mult == 3); occ = Schedule::Occurrence::Quarterly; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Monthly && mult == 3); occ = Schedule::Occurrence::EveryFourMonths; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Monthly && mult == 4); occ = Schedule::Occurrence::TwiceYearly; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Monthly && mult == 6); occ = Schedule::Occurrence::Yearly; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Yearly && mult == 1); occ = Schedule::Occurrence::EveryOtherYear; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Yearly && mult == 2); // Compound to Simple Occurrences occ = Schedule::Occurrence::Once; mult = 1; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Once && mult == 1); occ = Schedule::Occurrence::Daily; mult = 1; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Daily && mult == 1); occ = Schedule::Occurrence::Weekly; mult = 1; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Weekly && mult == 1); occ = Schedule::Occurrence::Weekly; mult = 2; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::EveryOtherWeek && mult == 1); // Schedule::Occurrence::Fortnightly not converted back occ = Schedule::Occurrence::EveryHalfMonth; mult = 1; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::EveryHalfMonth && mult == 1); occ = Schedule::Occurrence::Weekly; mult = 3; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::EveryThreeWeeks && mult == 1); occ = Schedule::Occurrence::Weekly ; mult = 4; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::EveryFourWeeks && mult == 1); occ = Schedule::Occurrence::Daily; mult = 30; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::EveryThirtyDays && mult == 1); occ = Schedule::Occurrence::Monthly; mult = 1; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Monthly && mult == 1); occ = Schedule::Occurrence::Weekly; mult = 8; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::EveryEightWeeks && mult == 1); occ = Schedule::Occurrence::Monthly; mult = 2; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::EveryOtherMonth && mult == 1); occ = Schedule::Occurrence::Monthly; mult = 3; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::EveryThreeMonths && mult == 1); // Schedule::Occurrence::Quarterly not converted back occ = Schedule::Occurrence::Monthly; mult = 4; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::EveryFourMonths && mult == 1); occ = Schedule::Occurrence::Monthly; mult = 6; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::TwiceYearly && mult == 1); occ = Schedule::Occurrence::Yearly; mult = 1; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Yearly && mult == 1); occ = Schedule::Occurrence::Yearly; mult = 2; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::EveryOtherYear && mult == 1); } void MyMoneyScheduleTest::testProcessingDates() { // There should be no processing calendar defined so // make sure fall back works MyMoneySchedule s; // Check there is no processing caledar defined. QVERIFY(s.processingCalendar() == nullptr); // This should be a processing day. QCOMPARE(s.isProcessingDate(QDate(2009, 12, 31)), true); // This should be a processing day when there is no calendar. QCOMPARE(s.isProcessingDate(QDate(2010, 1, 1)), true); // This should be a non-processing day as it is on a weekend. QCOMPARE(s.isProcessingDate(QDate(2010, 1, 2)), false); } void MyMoneyScheduleTest::testPaidEarlyOneTime() { // this tries to figure out what's wrong with // https://bugs.kde.org/show_bug.cgi?id=231029 MyMoneySchedule sch; QDate paymentInFuture = QDate::currentDate().addDays(7); QString ref_ok = QString( "\n" "\n" " \n" // krazy:exclude=spelling " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" ).arg(paymentInFuture.toString(Qt::ISODate)) .arg(paymentInFuture.toString(Qt::ISODate)) .arg(paymentInFuture.toString(Qt::ISODate)); QDomDocument doc; QDomElement node; doc.setContent(ref_ok); node = doc.documentElement().firstChild().toElement(); try { sch = MyMoneySchedule(node); QCOMPARE(sch.isFinished(), true); QCOMPARE(sch.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(sch.paymentDates(QDate::currentDate(), QDate::currentDate().addDays(21)).count(), 0); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyScheduleTest::testAdjustedNextPayment() { MyMoneySchedule s; QDate dueDate(2010, 5, 23); QDate adjustedDueDate(2010, 5, 21); s.setNextDueDate(dueDate); s.setOccurrence(Schedule::Occurrence::Monthly); s.setWeekendOption(Schedule::WeekendOption::MoveBefore); //if adjustedNextPayment works ok with adjusted date prior to the current date, it should return 2010-06-23 QDate nextDueDate(2010, 6, 23); //this is the current behaviour, and it is wrong //QCOMPARE(s.adjustedNextPayment(adjustedDueDate), adjustedDueDate); //this is the expected behaviour QCOMPARE(s.adjustedNextPayment(s.adjustedNextDueDate()), s.adjustedDate(nextDueDate, s.weekendOption())); } void MyMoneyScheduleTest::testReplaceId() { MyMoneySchedule sch; QDate paymentInFuture = QDate::currentDate().addDays(7); QString ref_ok = QString( "\n" "\n" " \n" // krazy:exclude=spelling " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" ).arg(paymentInFuture.toString(Qt::ISODate)) .arg(paymentInFuture.toString(Qt::ISODate)) .arg(paymentInFuture.toString(Qt::ISODate)); QDomDocument doc; QDomElement node; doc.setContent(ref_ok); node = doc.documentElement().firstChild().toElement(); try { sch = MyMoneySchedule(node); QCOMPARE(sch.transaction().postDate().isValid(), false); QCOMPARE(sch.transaction().splits()[0].accountId(), QLatin1String("A000076")); QCOMPARE(sch.transaction().splits()[1].accountId(), QLatin1String("A000276")); QCOMPARE(sch.replaceId(QLatin1String("A000079"), QLatin1String("A000076")), true); QCOMPARE(sch.transaction().splits()[0].accountId(), QLatin1String("A000079")); QCOMPARE(sch.transaction().splits()[1].accountId(), QLatin1String("A000276")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyScheduleTest::testAdjustedWhenItWillEnd() { MyMoneySchedule s; QDate endDate(2011, 8, 13); // this is a nonprocessing day because // it's a Saturday QDate refDate(2011, 8, 10); // just some ref date before the last payment s.setStartDate(endDate.addMonths(-1)); s.setOccurrence(Schedule::Occurrence::Monthly); s.setEndDate(endDate); // the next due date is on this day but the policy is to move the // schedule to the next processing day (Monday) s.setWeekendOption(Schedule::WeekendOption::MoveAfter); s.setNextDueDate(endDate); // the payment should be found between the respective date and one month after QCOMPARE(s.paymentDates(endDate, endDate.addMonths(1)).count(), 1); // the next payment must be the final one QCOMPARE(s.nextPayment(refDate), endDate); // and since it is on a Saturday, the adjusted one must be on the // following Monday QCOMPARE(s.adjustedNextPayment(refDate), endDate.addDays(2)); // reference for Sunday is still OK QCOMPARE(s.adjustedNextPayment(QDate(2011, 8, 14)), endDate.addDays(2)); // but it is finished on Monday (as reference date) QVERIFY(!s.adjustedNextPayment(QDate(2011, 8, 15)).isValid()); // check the # of remaining transactions s.setNextDueDate(endDate.addMonths(-1)); QCOMPARE(s.transactionsRemaining(), 2); } void MyMoneyScheduleTest::testElementNames() { for (auto i = (int)Schedule::Element::Payment; i <= (int)Schedule::Element::Payments; ++i) { auto isEmpty = MyMoneySchedulePrivate::getElName(static_cast(i)).isEmpty(); if (isEmpty) qWarning() << "Empty element's name " << i; QVERIFY(!isEmpty); } } void MyMoneyScheduleTest::testAttributeNames() { for (auto i = (int)Schedule::Attribute::Name; i < (int)Schedule::Attribute::LastAttribute; ++i) { auto isEmpty = MyMoneySchedulePrivate::getAttrName(static_cast(i)).isEmpty(); if (isEmpty) qWarning() << "Empty attribute's name " << i; QVERIFY(!isEmpty); } } void MyMoneyScheduleTest::testProcessLastDayInMonth() { MyMoneySchedule s; // occurrence is unrelated s.setOccurrence(Schedule::Occurrence::Any); s.setLastDayInMonth(true); s.setNextDueDate(QDate(2010, 1, 1)); QCOMPARE(s.adjustedNextDueDate(), QDate(2010,1,31)); s.setNextDueDate(QDate(2010, 2, 1)); QCOMPARE(s.adjustedNextDueDate(), QDate(2010,2,28)); s.setNextDueDate(QDate(2016, 2, 1)); QCOMPARE(s.adjustedNextDueDate(), QDate(2016,2,29)); s.setNextDueDate(QDate(2016, 4, 1)); QCOMPARE(s.adjustedNextDueDate(), QDate(2016,4,30)); s.setLastDayInMonth(false); QCOMPARE(s.adjustedNextDueDate(), QDate(2016,4,1)); } diff --git a/kmymoney/mymoney/tests/mymoneysplit-test.cpp b/kmymoney/mymoney/tests/mymoneysplit-test.cpp index 2aa04a8b2..6c6c083c3 100644 --- a/kmymoney/mymoney/tests/mymoneysplit-test.cpp +++ b/kmymoney/mymoney/tests/mymoneysplit-test.cpp @@ -1,515 +1,515 @@ /*************************************************************************** mymoneysplittest.cpp ------------------- copyright : (C) 2002 by Thomas Baumgart email : ipwizard@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneysplit-test.h" #include #include #include #define KMM_MYMONEY_UNIT_TESTABLE friend class MyMoneySplitTest; #include "mymoneymoney.h" #include "mymoneysplit.h" #include "mymoneysplit_p.h" #include "mymoneytransaction.h" #include "mymoneyexception.h" #include "mymoneyenums.h" QTEST_GUILESS_MAIN(MyMoneySplitTest) void MyMoneySplitTest::init() { m = new MyMoneySplit(); } void MyMoneySplitTest::cleanup() { delete m; } void MyMoneySplitTest::testEmptyConstructor() { QCOMPARE(m->accountId().isEmpty(), true); QCOMPARE(m->id().isEmpty(), true); QCOMPARE(m->memo().isEmpty(), true); QCOMPARE(m->action().isEmpty(), true); QCOMPARE(m->shares().isZero(), true); QCOMPARE(m->value().isZero(), true); QCOMPARE(m->reconcileFlag(), eMyMoney::Split::State::NotReconciled); QCOMPARE(m->reconcileDate(), QDate()); QCOMPARE(m->transactionId().isEmpty(), true); QCOMPARE(m->costCenterId().isEmpty(), true); } void MyMoneySplitTest::testSetFunctions() { m->setAccountId("Account"); m->setMemo("Memo"); m->setReconcileDate(QDate(1, 2, 3)); m->setReconcileFlag(eMyMoney::Split::State::Cleared); m->setShares(MyMoneyMoney(1234, 100)); m->setValue(MyMoneyMoney(3456, 100)); - m->setId("MyID"); + m->d_func()->setId("MyID"); m->setPayeeId("Payee"); m->setCostCenterId("CostCenter"); QList tagIdList; tagIdList << "Tag"; m->setTagIdList(tagIdList); m->setAction("Action"); m->setTransactionId("TestTransaction"); m->setValue("Key", "Value"); QCOMPARE(m->accountId(), QLatin1String("Account")); QCOMPARE(m->memo(), QLatin1String("Memo")); QCOMPARE(m->reconcileDate(), QDate(1, 2, 3)); QCOMPARE(m->reconcileFlag(), eMyMoney::Split::State::Cleared); QCOMPARE(m->shares(), MyMoneyMoney(1234, 100)); QCOMPARE(m->value(), MyMoneyMoney(3456, 100)); QCOMPARE(m->id(), QLatin1String("MyID")); QCOMPARE(m->payeeId(), QLatin1String("Payee")); QCOMPARE(m->tagIdList(), tagIdList); QCOMPARE(m->action(), QLatin1String("Action")); QCOMPARE(m->transactionId(), QLatin1String("TestTransaction")); QCOMPARE(m->value("Key"), QLatin1String("Value")); QCOMPARE(m->costCenterId(), QLatin1String("CostCenter")); } void MyMoneySplitTest::testCopyConstructor() { testSetFunctions(); MyMoneySplit n(*m); QCOMPARE(n.accountId(), QLatin1String("Account")); QCOMPARE(n.memo(), QLatin1String("Memo")); QCOMPARE(n.reconcileDate(), QDate(1, 2, 3)); QCOMPARE(n.reconcileFlag(), eMyMoney::Split::State::Cleared); QCOMPARE(n.shares(), MyMoneyMoney(1234, 100)); QCOMPARE(n.value(), MyMoneyMoney(3456, 100)); QCOMPARE(n.id(), QLatin1String("MyID")); QCOMPARE(n.payeeId(), QLatin1String("Payee")); QList tagIdList; tagIdList << "Tag"; QCOMPARE(n.tagIdList(), tagIdList); QCOMPARE(n.action(), QLatin1String("Action")); QCOMPARE(n.transactionId(), QLatin1String("TestTransaction")); QCOMPARE(n.value("Key"), QLatin1String("Value")); QCOMPARE(n.costCenterId(), QLatin1String("CostCenter")); } void MyMoneySplitTest::testAssignmentConstructor() { testSetFunctions(); MyMoneySplit n; n = *m; QCOMPARE(n.accountId(), QLatin1String("Account")); QCOMPARE(n.memo(), QLatin1String("Memo")); QCOMPARE(n.reconcileDate(), QDate(1, 2, 3)); QCOMPARE(n.reconcileFlag(), eMyMoney::Split::State::Cleared); QCOMPARE(n.shares(), MyMoneyMoney(1234, 100)); QCOMPARE(n.value(), MyMoneyMoney(3456, 100)); QCOMPARE(n.id(), QLatin1String("MyID")); QCOMPARE(n.payeeId(), QLatin1String("Payee")); QList tagIdList; tagIdList << QLatin1String("Tag"); QCOMPARE(n.tagIdList(), tagIdList); QCOMPARE(n.action(), QLatin1String("Action")); QCOMPARE(n.transactionId(), QLatin1String("TestTransaction")); QCOMPARE(n.value("Key"), QLatin1String("Value")); QCOMPARE(n.costCenterId(), QLatin1String("CostCenter")); } void MyMoneySplitTest::testEquality() { testSetFunctions(); MyMoneySplit n(*m); QCOMPARE(n, *m); } void MyMoneySplitTest::testInequality() { testSetFunctions(); MyMoneySplit n(*m); n.setShares(MyMoneyMoney(3456, 100)); QVERIFY(!(n == *m)); n = *m; - n.setId("Not My ID"); + n.d_func()->setId("Not My ID"); QVERIFY(!(n == *m)); n = *m; n.setPayeeId("No payee"); QVERIFY(!(n == *m)); n = *m; QList tagIdList; tagIdList << "No tag"; n.setTagIdList(tagIdList); QVERIFY(!(n == *m)); n = *m; n.setAction("No action"); QVERIFY(!(n == *m)); n = *m; n.setNumber("No number"); QVERIFY(!(n == *m)); n = *m; n.setAccountId("No account"); QVERIFY(!(n == *m)); n = *m; n.setMemo("No memo"); QVERIFY(!(n == *m)); n = *m; n.setReconcileDate(QDate(3, 4, 5)); QVERIFY(!(n == *m)); n = *m; n.setReconcileFlag(eMyMoney::Split::State::Frozen); QVERIFY(!(n == *m)); n = *m; n.setShares(MyMoneyMoney(4567, 100)); QVERIFY(!(n == *m)); n = *m; n.setValue(MyMoneyMoney(9876, 100)); QVERIFY(!(n == *m)); n = *m; n.setTransactionId("NoTransaction"); QVERIFY(!(n == *m)); n = *m; n.setValue("Key", "NoValue"); QVERIFY(!(n == *m)); n = *m; n.setCostCenterId("NoCostCenter"); QVERIFY(!(n == *m)); } void MyMoneySplitTest::testUnaryMinus() { testSetFunctions(); MyMoneySplit n = -*m; QCOMPARE(n.accountId(), QLatin1String("Account")); QCOMPARE(n.memo(), QLatin1String("Memo")); QCOMPARE(n.reconcileDate(), QDate(1, 2, 3)); QCOMPARE(n.reconcileFlag(), eMyMoney::Split::State::Cleared); QCOMPARE(n.shares(), MyMoneyMoney(-1234, 100)); QCOMPARE(n.value(), MyMoneyMoney(-3456, 100)); QCOMPARE(n.id(), QLatin1String("MyID")); QCOMPARE(n.payeeId(), QLatin1String("Payee")); QList tagIdList; tagIdList << "Tag"; QCOMPARE(n.tagIdList(), tagIdList); QCOMPARE(n.action(), QLatin1String("Action")); QCOMPARE(n.transactionId(), QLatin1String("TestTransaction")); QCOMPARE(n.value("Key"), QLatin1String("Value")); QCOMPARE(n.costCenterId(), QLatin1String("CostCenter")); } void MyMoneySplitTest::testAmortization() { QCOMPARE(m->isAmortizationSplit(), false); testSetFunctions(); QCOMPARE(m->isAmortizationSplit(), false); m->setAction(MyMoneySplit::ActionAmortization); QCOMPARE(m->isAmortizationSplit(), true); } void MyMoneySplitTest::testValue() { m->setValue(MyMoneyMoney(1, 100)); m->setShares(MyMoneyMoney(2, 100)); QCOMPARE(m->value("EUR", "EUR"), MyMoneyMoney(1, 100)); QCOMPARE(m->value("EUR", "USD"), MyMoneyMoney(2, 100)); } void MyMoneySplitTest::testSetValue() { QCOMPARE(m->value().isZero(), true); QCOMPARE(m->shares().isZero(), true); m->setValue(MyMoneyMoney(1, 100), "EUR", "EUR"); QCOMPARE(m->value(), MyMoneyMoney(1, 100)); QCOMPARE(m->shares().isZero(), true); m->setValue(MyMoneyMoney(3, 100), "EUR", "USD"); QCOMPARE(m->value(), MyMoneyMoney(1, 100)); QCOMPARE(m->shares(), MyMoneyMoney(3, 100)); } void MyMoneySplitTest::testSetAction() { QCOMPARE(m->action().isEmpty(), true); m->setAction(eMyMoney::Split::InvestmentTransactionType::BuyShares); QCOMPARE(m->action(), QLatin1String(MyMoneySplit::ActionBuyShares)); m->setAction(eMyMoney::Split::InvestmentTransactionType::SellShares); QCOMPARE(m->action(), QLatin1String(MyMoneySplit::ActionBuyShares)); m->setAction(eMyMoney::Split::InvestmentTransactionType::Dividend); QCOMPARE(m->action(), QLatin1String(MyMoneySplit::ActionDividend)); m->setAction(eMyMoney::Split::InvestmentTransactionType::Yield); QCOMPARE(m->action(), QLatin1String(MyMoneySplit::ActionYield)); m->setAction(eMyMoney::Split::InvestmentTransactionType::ReinvestDividend); QCOMPARE(m->action(), QLatin1String(MyMoneySplit::ActionReinvestDividend)); m->setAction(eMyMoney::Split::InvestmentTransactionType::AddShares); QCOMPARE(m->action(), QLatin1String(MyMoneySplit::ActionAddShares)); m->setAction(eMyMoney::Split::InvestmentTransactionType::RemoveShares); QCOMPARE(m->action(), QLatin1String(MyMoneySplit::ActionAddShares)); m->setAction(eMyMoney::Split::InvestmentTransactionType::SplitShares); QCOMPARE(m->action(), QLatin1String(MyMoneySplit::ActionSplitShares)); } void MyMoneySplitTest::testIsAutoCalc() { QCOMPARE(m->isAutoCalc(), false); m->setValue(MyMoneyMoney::autoCalc); QCOMPARE(m->isAutoCalc(), true); m->setShares(MyMoneyMoney::autoCalc); QCOMPARE(m->isAutoCalc(), true); m->setValue(MyMoneyMoney()); QCOMPARE(m->isAutoCalc(), true); m->setShares(MyMoneyMoney(1, 100)); QCOMPARE(m->isAutoCalc(), false); } void MyMoneySplitTest::testWriteXML() { MyMoneySplit s; s.setPayeeId("P000001"); QList tagIdList; tagIdList << "G000001"; s.setTagIdList(tagIdList); s.setShares(MyMoneyMoney(96379, 100)); s.setValue(MyMoneyMoney(96379, 1000)); s.setAccountId("A000076"); s.setCostCenterId("C000005"); s.setNumber("124"); s.setBankID("SPID"); s.setAction(MyMoneySplit::ActionDeposit); s.setReconcileFlag(eMyMoney::Split::State::Reconciled); QDomDocument doc("TEST"); QDomElement el = doc.createElement("SPLIT-CONTAINER"); doc.appendChild(el); s.writeXML(doc, el); QCOMPARE(doc.doctype().name(), QLatin1String("TEST")); QDomElement splitContainer = doc.documentElement(); QCOMPARE(splitContainer.tagName(), QLatin1String("SPLIT-CONTAINER")); QCOMPARE(splitContainer.childNodes().size(), 1); QCOMPARE(splitContainer.childNodes().at(0).isElement(), true); QDomElement split = splitContainer.childNodes().at(0).toElement(); QCOMPARE(split.tagName(), QLatin1String("SPLIT")); QCOMPARE(split.attribute("payee"), QLatin1String("P000001")); QCOMPARE(split.attribute("reconcileflag"), QLatin1String("2")); QCOMPARE(split.attribute("shares"), QLatin1String("96379/100")); QCOMPARE(split.attribute("reconciledate"), QString()); QCOMPARE(split.attribute("action"), QLatin1String("Deposit")); QCOMPARE(split.attribute("bankid"), QLatin1String("SPID")); QCOMPARE(split.attribute("account"), QLatin1String("A000076")); QCOMPARE(split.attribute("costcenter"), QLatin1String("C000005")); QCOMPARE(split.attribute("number"), QLatin1String("124")); QCOMPARE(split.attribute("value"), QLatin1String("96379/1000")); QCOMPARE(split.attribute("memo"), QString()); QCOMPARE(split.attribute("id"), QString()); QCOMPARE(split.childNodes().size(), 1); QCOMPARE(split.childNodes().at(0).isElement(), true); QDomElement tag = split.childNodes().at(0).toElement(); QCOMPARE(tag.tagName(), QLatin1String("TAG")); QCOMPARE(tag.attribute("id"), QLatin1String("G000001")); QCOMPARE(tag.childNodes().size(), 0); QString ref = QString( "\n" "\n" " \n" " \n" " \n" "\n"); } void MyMoneySplitTest::testReadXML() { MyMoneySplit s; QString ref_ok = QString( "\n" "\n" " \n" " \n" " \n" "\n"); QString ref_false = QString( "\n" "\n" " \n" " \n" "\n"); QDomDocument doc; QDomElement node; doc.setContent(ref_false); node = doc.documentElement().firstChild().toElement(); try { s = MyMoneySplit(node); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { } doc.setContent(ref_ok); node = doc.documentElement().firstChild().toElement(); try { s = MyMoneySplit(node); QCOMPARE(s.id().isEmpty(), true); QCOMPARE(s.payeeId(), QLatin1String("P000001")); QList tagIdList; tagIdList << QLatin1String("G000001"); QCOMPARE(s.tagIdList(), tagIdList); QCOMPARE(s.reconcileDate(), QDate()); QCOMPARE(s.shares(), MyMoneyMoney(96379, 100)); QCOMPARE(s.value(), MyMoneyMoney(96379, 1000)); QCOMPARE(s.number(), QLatin1String("124")); QCOMPARE(s.bankID(), QLatin1String("SPID")); QCOMPARE(s.reconcileFlag(), eMyMoney::Split::State::Reconciled); QCOMPARE(s.action(), QLatin1String(MyMoneySplit::ActionDeposit)); QCOMPARE(s.accountId(), QLatin1String("A000076")); QCOMPARE(s.costCenterId(), QLatin1String("C000005")); QCOMPARE(s.memo(), QLatin1String("MyMemo")); } catch (const MyMoneyException &) { } } void MyMoneySplitTest::testReplaceId() { MyMoneySplit s; bool changed; s.setPayeeId("P000001"); s.setAccountId("A000076"); s.setCostCenterId("C000005"); changed = s.replaceId("X0001", "Y00001"); QCOMPARE(changed, false); QCOMPARE(s.payeeId(), QLatin1String("P000001")); QCOMPARE(s.accountId(), QLatin1String("A000076")); QCOMPARE(s.costCenterId(), QLatin1String("C000005")); changed = s.replaceId("P000002", "P000001"); QCOMPARE(changed, true); QCOMPARE(s.payeeId(), QLatin1String("P000002")); QCOMPARE(s.accountId(), QLatin1String("A000076")); QCOMPARE(s.costCenterId(), QLatin1String("C000005")); changed = s.replaceId("A000079", "A000076"); QCOMPARE(changed, true); QCOMPARE(s.payeeId(), QLatin1String("P000002")); QCOMPARE(s.accountId(), QLatin1String("A000079")); QCOMPARE(s.costCenterId(), QLatin1String("C000005")); changed = s.replaceId("C000006", "C000005"); QCOMPARE(changed, true); QCOMPARE(s.payeeId(), QLatin1String("P000002")); QCOMPARE(s.accountId(), QLatin1String("A000079")); QCOMPARE(s.costCenterId(), QLatin1String("C000006")); QString ref_ok = QString( "\n" "\n" " \n" " \n" " \n" " \n" " &lt;CONTAINER>\n" " &lt;TRANSACTION postdate="2010-03-05" memo="UMBUCHUNG" id="" commodity="EUR" entrydate="2010-03-08" >\n" " &lt;SPLITS>\n" " &lt;SPLIT payee="P000010" reconciledate="" shares="125000/100" action="Transfer" bankid="" number="" reconcileflag="0" memo="UMBUCHUNG" value="125000/100" id="S0001" account="A000087" />\n" " &lt;SPLIT payee="P000011" reconciledate="" shares="-125000/100" action="" bankid="A000076-2010-03-05-b6850c0-1" number="" reconcileflag="0" memo="UMBUCHUNG" value="-125000/100" id="S0002" account="A000076" />\n" " &lt;/SPLITS>\n" " &lt;KEYVALUEPAIRS>\n" " &lt;PAIR key="Imported" value="true" />\n" " &lt;/KEYVALUEPAIRS>\n" " &lt;/TRANSACTION>\n" " &lt;/CONTAINER>\n" "\" />\n" " \n" " \n" " \n" "\n" ); QDomDocument doc; QDomElement node; doc.setContent(ref_ok); node = doc.documentElement().firstChild().toElement(); try { s = MyMoneySplit(node); QCOMPARE(s.payeeId(), QLatin1String("P000001")); QCOMPARE(s.replaceId("P2", "P1"), false); QCOMPARE(s.matchedTransaction().splits()[0].payeeId(), QLatin1String("P000010")); QCOMPARE(s.matchedTransaction().splits()[1].payeeId(), QLatin1String("P000011")); QCOMPARE(s.replaceId("P0010", "P000010"), true); QCOMPARE(s.matchedTransaction().splits()[0].payeeId(), QLatin1String("P0010")); QCOMPARE(s.matchedTransaction().splits()[1].payeeId(), QLatin1String("P000011")); QCOMPARE(s.replaceId("P0011", "P000011"), true); QCOMPARE(s.matchedTransaction().splits()[0].payeeId(), QLatin1String("P0010")); QCOMPARE(s.matchedTransaction().splits()[1].payeeId(), QLatin1String("P0011")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneySplitTest::testElementNames() { for (auto i = (int)Split::Element::Split; i <= (int)Split::Element::KeyValuePairs; ++i) { auto isEmpty = MyMoneySplitPrivate::getElName(static_cast(i)).isEmpty(); if (isEmpty) qWarning() << "Empty element's name " << i; QVERIFY(!isEmpty); } } void MyMoneySplitTest::testAttributeNames() { for (auto i = (int)Split::Attribute::ID; i < (int)Split::Attribute::LastAttribute; ++i) { auto isEmpty = MyMoneySplitPrivate::getAttrName(static_cast(i)).isEmpty(); if (isEmpty) qWarning() << "Empty attribute's name " << i; QVERIFY(!isEmpty); } } diff --git a/kmymoney/mymoney/tests/mymoneytag-test.cpp b/kmymoney/mymoney/tests/mymoneytag-test.cpp index a18db6e7e..4cd629474 100644 --- a/kmymoney/mymoney/tests/mymoneytag-test.cpp +++ b/kmymoney/mymoney/tests/mymoneytag-test.cpp @@ -1,51 +1,51 @@ /*************************************************************************** mymoneytagtest.cpp ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneytag-test.h" #include #include #include #define KMM_MYMONEY_UNIT_TESTABLE friend class MyMoneyTagTest; #include "mymoneytag.h" #include "mymoneytag_p.h" using namespace std; QTEST_GUILESS_MAIN(MyMoneyTagTest) void MyMoneyTagTest::testXml() { QDomDocument doc; QDomElement parent = doc.createElement("Test"); doc.appendChild(parent); MyMoneyTag tag1; - tag1.m_id = "some random id";//if the ID isn't set, w ethrow an exception + tag1.d_func()->m_id = "some random id";//if the ID isn't set, w ethrow an exception tag1.writeXML(doc, parent); QDomElement el = parent.firstChild().toElement(); QVERIFY(!el.isNull()); MyMoneyTag tag2(el); } void MyMoneyTagTest::testAttributeNames() { for (auto i = (int)Tag::Attribute::Name; i < (int)Tag::Attribute::LastAttribute; ++i) { auto isEmpty = MyMoneyTagPrivate::getAttrName(static_cast(i)).isEmpty(); if (isEmpty) qWarning() << "Empty attribute's name " << i; QVERIFY(!isEmpty); } } diff --git a/kmymoney/mymoney/tests/mymoneytransaction-test.cpp b/kmymoney/mymoney/tests/mymoneytransaction-test.cpp index e42116f1b..3000ff33e 100644 --- a/kmymoney/mymoney/tests/mymoneytransaction-test.cpp +++ b/kmymoney/mymoney/tests/mymoneytransaction-test.cpp @@ -1,737 +1,738 @@ /*************************************************************************** mymoneytransactiontest.cpp ------------------- copyright : (C) 2002 by Thomas Baumgart email : ipwizard@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneytransaction-test.h" #include #include #include #include #include "mymoneytestutils.h" #include "mymoneymoney.h" #include "mymoneysplit.h" +#include "mymoneysplit_p.h" #include "mymoneytransaction.h" #include "mymoneytransaction_p.h" #include "mymoneyenums.h" QTEST_GUILESS_MAIN(MyMoneyTransactionTest) void MyMoneyTransactionTest::init() { m = new MyMoneyTransaction(); } void MyMoneyTransactionTest::cleanup() { delete m; } void MyMoneyTransactionTest::testEmptyConstructor() { QVERIFY(m->id().isEmpty()); QVERIFY(m->entryDate() == QDate()); QVERIFY(m->memo().isEmpty()); QVERIFY(m->splits().count() == 0); } void MyMoneyTransactionTest::testSetFunctions() { m->setMemo("Memo"); m->setPostDate(QDate(1, 2, 3)); QVERIFY(m->postDate() == QDate(1, 2, 3)); QVERIFY(m->memo() == "Memo"); } void MyMoneyTransactionTest::testConstructor() { testSetFunctions(); MyMoneyTransaction a("ID", *m); QVERIFY(a.id() == "ID"); QVERIFY(a.entryDate() == QDate::currentDate()); QVERIFY(a.memo() == "Memo"); QVERIFY(a.postDate() == QDate(1, 2, 3)); } void MyMoneyTransactionTest::testCopyConstructor() { testConstructor(); MyMoneyTransaction a("ID", *m); a.setValue("Key", "Value"); MyMoneyTransaction n(a); QVERIFY(n.id() == "ID"); QVERIFY(n.entryDate() == QDate::currentDate()); QVERIFY(n.memo() == "Memo"); QVERIFY(n.postDate() == QDate(1, 2, 3)); QVERIFY(n.value("Key") == "Value"); } void MyMoneyTransactionTest::testAssignmentConstructor() { testConstructor(); MyMoneyTransaction a("ID", *m); a.setValue("Key", "Value"); MyMoneyTransaction n; n = a; QVERIFY(n.id() == "ID"); QVERIFY(n.entryDate() == QDate::currentDate()); QVERIFY(n.memo() == "Memo"); QVERIFY(n.postDate() == QDate(1, 2, 3)); QVERIFY(n.value("Key") == "Value"); } void MyMoneyTransactionTest::testEquality() { testConstructor(); MyMoneyTransaction n(*m); QVERIFY(n == *m); QVERIFY(!(n != *m)); } void MyMoneyTransactionTest::testInequality() { testConstructor(); MyMoneyTransaction n(*m); n.setPostDate(QDate(1, 1, 1)); QVERIFY(!(n == *m)); QVERIFY(n != *m); n = *m; n.setValue("key", "value"); QVERIFY(!(n == *m)); QVERIFY(n != *m); } void MyMoneyTransactionTest::testAddSplits() { - m->setId("TestID"); + m->d_func()->setId("TestID"); MyMoneySplit split1, split2; split1.setAccountId("A000001"); split2.setAccountId("A000002"); split1.setValue(MyMoneyMoney(100, 100)); split2.setValue(MyMoneyMoney(200, 100)); try { QVERIFY(m->accountReferenced("A000001") == false); QVERIFY(m->accountReferenced("A000002") == false); m->addSplit(split1); m->addSplit(split2); QVERIFY(m->splitCount() == 2); QVERIFY(m->splits()[0].accountId() == "A000001"); QVERIFY(m->splits()[1].accountId() == "A000002"); QVERIFY(m->accountReferenced("A000001") == true); QVERIFY(m->accountReferenced("A000002") == true); QVERIFY(m->splits()[0].id() == "S0001"); QVERIFY(m->splits()[1].id() == "S0002"); QVERIFY(split1.id() == "S0001"); QVERIFY(split2.id() == "S0002"); QVERIFY(m->splits()[0].transactionId() == "TestID"); QVERIFY(m->splits()[1].transactionId() == "TestID"); QVERIFY(split1.transactionId() == "TestID"); QVERIFY(split2.transactionId() == "TestID"); } catch (const MyMoneyException &e) { unexpectedException(e); } // try to add split with assigned ID try { m->addSplit(split1); QFAIL("Exception expected!"); } catch (const MyMoneyException &) { } } void MyMoneyTransactionTest::testModifySplits() { testAddSplits(); MyMoneySplit split; split = m->splits()[0]; split.setAccountId("A000003"); - split.setId("S00000000"); + split.d_func()->setId("S00000000"); // this one should fail, because the ID is invalid try { m->modifySplit(split); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } // set id to correct value, and check that it worked - split.setId("S0001"); + split.d_func()->setId("S0001"); try { m->modifySplit(split); QVERIFY(m->splitCount() == 2); QVERIFY(m->splits()[0].accountId() == "A000003"); QVERIFY(m->splits()[1].accountId() == "A000002"); QVERIFY(m->accountReferenced("A000001") == false); QVERIFY(m->accountReferenced("A000002") == true); QVERIFY(m->splits()[0].id() == "S0001"); QVERIFY(m->splits()[1].id() == "S0002"); QVERIFY(split.id() == "S0001"); QVERIFY(split.accountId() == "A000003"); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyTransactionTest::testDeleteSplits() { testAddSplits(); MyMoneySplit split; // add a third split split.setAccountId("A000003"); split.setValue(MyMoneyMoney(300, 100)); try { m->addSplit(split); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } - split.setId("S00000000"); + split.d_func()->setId("S00000000"); // this one should fail, because the ID is invalid try { m->modifySplit(split); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } // set id to correct value, and check that it worked - split.setId("S0002"); + split.d_func()->setId("S0002"); try { m->removeSplit(split); QVERIFY(m->splitCount() == 2); QVERIFY(m->splits()[0].accountId() == "A000001"); QVERIFY(m->accountReferenced("A000002") == false); QVERIFY(m->accountReferenced("A000001") == true); QVERIFY(m->accountReferenced("A000003") == true); QVERIFY(m->splits()[0].id() == "S0001"); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } // set id to the other correct value, and check that it worked - split.setId("S0003"); + split.d_func()->setId("S0003"); try { m->removeSplit(split); QVERIFY(m->splitCount() == 1); QVERIFY(m->accountReferenced("A000001") == true); QVERIFY(m->accountReferenced("A000003") == false); QVERIFY(m->splits()[0].id() == "S0001"); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyTransactionTest::testDeleteAllSplits() { testAddSplits(); try { m->removeSplits(); QVERIFY(m->splitCount() == 0); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyTransactionTest::testExtractSplit() { testAddSplits(); MyMoneySplit split; // this one should fail, as the account is not referenced by // any split in the transaction try { split = m->splitByAccount(QString("A000003")); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } // this one should be found try { split = m->splitByAccount(QString("A000002")); QVERIFY(split.id() == "S0002"); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } // this one should be found also try { split = m->splitByAccount(QString("A000002"), false); QVERIFY(split.id() == "S0001"); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyTransactionTest::testSplitSum() { QVERIFY(m->splitSum().isZero()); testAddSplits(); MyMoneySplit s1, s2; s1 = m->splits()[0]; s1.setValue(MyMoneyMoney()); s2 = m->splits()[1]; s2.setValue(MyMoneyMoney()); m->modifySplit(s1); m->modifySplit(s2); QVERIFY(m->splitSum().isZero()); s1.setValue(MyMoneyMoney(1234, 100)); m->modifySplit(s1); QVERIFY(m->splitSum() == MyMoneyMoney(1234, 100)); s2.setValue(MyMoneyMoney(-1234, 100)); m->modifySplit(s2); QVERIFY(m->splitSum().isZero()); s1.setValue(MyMoneyMoney(5678, 100)); m->modifySplit(s1); QVERIFY(m->splitSum() == MyMoneyMoney(4444, 100)); } void MyMoneyTransactionTest::testIsLoanPayment() { testAddSplits(); QVERIFY(m->isLoanPayment() == false); MyMoneySplit s1, s2; s1 = m->splits()[0]; s2 = m->splits()[1]; s1.setAction(MyMoneySplit::ActionAmortization); m->modifySplit(s1); QVERIFY(m->isLoanPayment() == true); s1.setAction(MyMoneySplit::ActionWithdrawal); m->modifySplit(s1); QVERIFY(m->isLoanPayment() == false); s2.setAction(MyMoneySplit::ActionAmortization); m->modifySplit(s2); QVERIFY(m->isLoanPayment() == true); s2.setAction(MyMoneySplit::ActionWithdrawal); m->modifySplit(s2); QVERIFY(m->isLoanPayment() == false); } #if 0 void MyMoneyTransactionTest::testAddDuplicateAccount() { testAddSplits(); qDebug() << "Split count is" << m->splitCount(); MyMoneySplit split1, split2; split1.setAccountId("A000001"); split2.setAccountId("A000002"); split1.setValue(MyMoneyMoney(100)); split2.setValue(MyMoneyMoney(200)); try { QVERIFY(m->accountReferenced("A000001") == true); QVERIFY(m->accountReferenced("A000002") == true); m->addSplit(split1); m->addSplit(split2); qDebug() << "Split count is" << m->splitCount(); QVERIFY(m->splitCount() == 2); QVERIFY(m->splits()[0].accountId() == "A000001"); QVERIFY(m->splits()[1].accountId() == "A000002"); QVERIFY(m->accountReferenced("A000001") == true); QVERIFY(m->accountReferenced("A000002") == true); QVERIFY(m->splits()[0].id() == "S0001"); QVERIFY(m->splits()[1].id() == "S0002"); QVERIFY(split1.id() == "S0001"); QVERIFY(split2.id() == "S0002"); } catch (const MyMoneyException &e) { unexpectedException(e); } QVERIFY(m->splits()[0].value() == MyMoneyMoney(200)); QVERIFY(m->splits()[1].value() == MyMoneyMoney(400)); } void MyMoneyTransactionTest::testModifyDuplicateAccount() { testAddSplits(); MyMoneySplit split; split = m->splitByAccount(QString("A000002")); split.setAccountId("A000001"); try { m->modifySplit(split); QVERIFY(m->splitCount() == 1); QVERIFY(m->accountReferenced("A000001") == true); QVERIFY(m->splits()[0].value() == MyMoneyMoney(300)); } catch (const MyMoneyException &e) { unexpectedException(e); } } #endif void MyMoneyTransactionTest::testWriteXML() { MyMoneyTransaction t; t.setPostDate(QDate(2001, 12, 28)); t.setEntryDate(QDate(2003, 9, 29)); - t.setId("T000000000000000001"); + t.d_func()->setId("T000000000000000001"); t.setMemo("Wohnung:Miete"); t.setCommodity("EUR"); t.setValue("key", "value"); MyMoneySplit s; s.setPayeeId("P000001"); QList tagIdList; tagIdList << "G000001"; s.setTagIdList(tagIdList); s.setShares(MyMoneyMoney(96379, 100)); s.setValue(MyMoneyMoney(96379, 100)); s.setAction(MyMoneySplit::ActionWithdrawal); s.setAccountId("A000076"); s.setReconcileFlag(eMyMoney::Split::State::Reconciled); s.setBankID("SPID"); t.addSplit(s); QDomDocument doc("TEST"); QDomElement el = doc.createElement("TRANSACTION-CONTAINER"); doc.appendChild(el); t.writeXML(doc, el); QCOMPARE(doc.doctype().name(), QLatin1String("TEST")); QDomElement transactionContainer = doc.documentElement(); QVERIFY(transactionContainer.isElement()); QCOMPARE(transactionContainer.tagName(), QLatin1String("TRANSACTION-CONTAINER")); QVERIFY(transactionContainer.childNodes().size() == 1); QVERIFY(transactionContainer.childNodes().at(0).isElement()); QDomElement transaction = transactionContainer.childNodes().at(0).toElement(); QCOMPARE(transaction.tagName(), QLatin1String("TRANSACTION")); QCOMPARE(transaction.attribute("id"), QLatin1String("T000000000000000001")); QCOMPARE(transaction.attribute("postdate"), QLatin1String("2001-12-28")); QCOMPARE(transaction.attribute("commodity"), QLatin1String("EUR")); QCOMPARE(transaction.attribute("memo"), QLatin1String("Wohnung:Miete")); QCOMPARE(transaction.attribute("entrydate"), QLatin1String("2003-09-29")); QCOMPARE(transaction.childNodes().size(), 2); QVERIFY(transaction.childNodes().at(0).isElement()); QDomElement splits = transaction.childNodes().at(0).toElement(); QCOMPARE(splits.tagName(), QLatin1String("SPLITS")); QCOMPARE(splits.childNodes().size(), 1); QVERIFY(splits.childNodes().at(0).isElement()); QDomElement split = splits.childNodes().at(0).toElement(); QCOMPARE(split.tagName(), QLatin1String("SPLIT")); QCOMPARE(split.attribute("id"), QLatin1String("S0001")); QCOMPARE(split.attribute("payee"), QLatin1String("P000001")); QCOMPARE(split.attribute("reconcileflag"), QLatin1String("2")); QCOMPARE(split.attribute("shares"), QLatin1String("96379/100")); QCOMPARE(split.attribute("reconciledate"), QString()); QCOMPARE(split.attribute("action"), QLatin1String("Withdrawal")); QCOMPARE(split.attribute("bankid"), QLatin1String("SPID")); QCOMPARE(split.attribute("account"), QLatin1String("A000076")); QCOMPARE(split.attribute("number"), QString()); QCOMPARE(split.attribute("value"), QLatin1String("96379/100")); QCOMPARE(split.attribute("memo"), QString()); QCOMPARE(split.childNodes().size(), 1); QVERIFY(split.childNodes().at(0).isElement()); QDomElement tag = split.childNodes().at(0).toElement(); QCOMPARE(tag.tagName(), QLatin1String("TAG")); QCOMPARE(tag.attribute("id"), QLatin1String("G000001")); QCOMPARE(tag.childNodes().size(), 0); QDomElement keyValuePairs = transaction.childNodes().at(1).toElement(); QCOMPARE(keyValuePairs.tagName(), QLatin1String("KEYVALUEPAIRS")); QCOMPARE(keyValuePairs.childNodes().size(), 1); QVERIFY(keyValuePairs.childNodes().at(0).isElement()); QDomElement keyValuePair1 = keyValuePairs.childNodes().at(0).toElement(); QCOMPARE(keyValuePair1.tagName(), QLatin1String("PAIR")); QCOMPARE(keyValuePair1.attribute("key"), QLatin1String("key")); QCOMPARE(keyValuePair1.attribute("value"), QLatin1String("value")); QCOMPARE(keyValuePair1.childNodes().size(), 0); } void MyMoneyTransactionTest::testReadXML() { MyMoneyTransaction t; QString ref_ok = QString( "\n" "\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" ); QString ref_false = QString( "\n" "\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" ); QDomDocument doc; QDomElement node; doc.setContent(ref_false); node = doc.documentElement().firstChild().toElement(); try { t = MyMoneyTransaction(node); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { } doc.setContent(ref_ok); node = doc.documentElement().firstChild().toElement(); t.setValue("key", "VALUE"); try { t = MyMoneyTransaction(node); QVERIFY(t.postDate() == QDate(2001, 12, 28)); QVERIFY(t.entryDate() == QDate(2003, 9, 29)); QVERIFY(t.id() == "T000000000000000001"); QVERIFY(t.memo() == "Wohnung:Miete"); QVERIFY(t.commodity() == "EUR"); QVERIFY(t.pairs().count() == 1); QVERIFY(t.value("key") == "value"); QVERIFY(t.splits().count() == 1); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyTransactionTest::testReadXMLEx() { MyMoneyTransaction t; QString ref_ok = QString( "\n" "\n" "\n" " \n" " \n" " \n" " \n" " \n" " &lt;CONTAINER>\n" " &lt;TRANSACTION postdate="2010-03-05" memo="UMBUCHUNG" id="" commodity="EUR" entrydate="2010-03-08" >\n" " &lt;SPLITS>\n" " &lt;SPLIT payee="P000010" reconciledate="" shares="125000/100" action="Transfer" bankid="" number="" reconcileflag="0" memo="UMBUCHUNG" value="125000/100" id="S0001" account="A000087" />\n" " &lt;SPLIT payee="P000010" reconciledate="" shares="-125000/100" action="" bankid="A000076-2010-03-05-b6850c0-1" number="" reconcileflag="0" memo="UMBUCHUNG" value="-125000/100" id="S0002" account="A000076" />\n" " &lt;/SPLITS>\n" " &lt;KEYVALUEPAIRS>\n" " &lt;PAIR key="Imported" value="true" />\n" " &lt;/KEYVALUEPAIRS>\n" " &lt;/TRANSACTION>\n" " &lt;/CONTAINER>\n" "\" />\n" " \n" " \n" " \n" " \n" " \n" "\n" "\n" ); QDomDocument doc; QDomElement node; doc.setContent(ref_ok); node = doc.documentElement().firstChild().toElement(); try { t = MyMoneyTransaction(node); QVERIFY(t.pairs().count() == 0); QVERIFY(t.splits().size() == 2); QVERIFY(t.splits()[0].pairs().count() == 3); QVERIFY(t.splits()[1].pairs().count() == 0); QVERIFY(t.splits()[0].isMatched()); MyMoneyTransaction ti = t.splits()[0].matchedTransaction(); QVERIFY(ti.pairs().count() == 1); QVERIFY(ti.isImported()); QVERIFY(ti.splits().count() == 2); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyTransactionTest::testHasReferenceTo() { MyMoneyTransaction t; t.setPostDate(QDate(2001, 12, 28)); t.setEntryDate(QDate(2003, 9, 29)); - t.setId("T000000000000000001"); + t.d_func()->setId("T000000000000000001"); t.setMemo("Wohnung:Miete"); t.setCommodity("EUR"); t.setValue("key", "value"); MyMoneySplit s; s.setPayeeId("P000001"); QList tagIdList; tagIdList << "G000001"; s.setTagIdList(tagIdList); s.setShares(MyMoneyMoney(96379, 100)); s.setValue(MyMoneyMoney(96379, 100)); s.setAction(MyMoneySplit::ActionWithdrawal); s.setAccountId("A000076"); s.setReconcileFlag(eMyMoney::Split::State::Reconciled); t.addSplit(s); QVERIFY(t.hasReferenceTo("EUR") == true); QVERIFY(t.hasReferenceTo("P000001") == true); QVERIFY(t.hasReferenceTo("G000001") == true); QVERIFY(t.hasReferenceTo("A000076") == true); } void MyMoneyTransactionTest::testAutoCalc() { QVERIFY(m->hasAutoCalcSplit() == false); testAddSplits(); QVERIFY(m->hasAutoCalcSplit() == false); MyMoneySplit split; split = m->splits()[0]; split.setShares(MyMoneyMoney::autoCalc); split.setValue(MyMoneyMoney::autoCalc); m->modifySplit(split); QVERIFY(m->hasAutoCalcSplit() == true); } void MyMoneyTransactionTest::testIsStockSplit() { QVERIFY(m->isStockSplit() == false); testAddSplits(); QVERIFY(m->isStockSplit() == false); m->removeSplits(); MyMoneySplit s; s.setShares(MyMoneyMoney(1, 2)); s.setAction(MyMoneySplit::ActionSplitShares); s.setAccountId("A0001"); m->addSplit(s); QVERIFY(m->isStockSplit() == true); } void MyMoneyTransactionTest::testAddMissingAccountId() { MyMoneySplit s; s.setShares(MyMoneyMoney(1, 2)); try { m->addSplit(s); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { } } void MyMoneyTransactionTest::testModifyMissingAccountId() { testAddSplits(); MyMoneySplit s = m->splits()[0]; s.setAccountId(QString()); try { m->modifySplit(s); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { } } void MyMoneyTransactionTest::testReplaceId() { testAddSplits(); bool changed; try { changed = m->replaceId("Joe", "Bla"); QVERIFY(changed == false); QVERIFY(m->splits()[0].accountId() == "A000001"); QVERIFY(m->splits()[1].accountId() == "A000002"); changed = m->replaceId("A000003", "A000001"); QVERIFY(changed == true); QVERIFY(m->splits()[0].accountId() == "A000003"); QVERIFY(m->splits()[1].accountId() == "A000002"); changed = m->replaceId("A000004", "A000002"); QVERIFY(changed == true); QVERIFY(m->splits()[0].accountId() == "A000003"); QVERIFY(m->splits()[1].accountId() == "A000004"); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyTransactionTest::testElementNames() { for (auto i = (int)Transaction::Element::Split; i <= (int)Transaction::Element::Splits; ++i) { auto isEmpty = MyMoneyTransactionPrivate::getElName(static_cast(i)).isEmpty(); if (isEmpty) qWarning() << "Empty element's name " << i; QVERIFY(!isEmpty); } } void MyMoneyTransactionTest::testAttributeNames() { for (auto i = (int)Transaction::Attribute::Name; i < (int)Transaction::Attribute::LastAttribute; ++i) { auto isEmpty = MyMoneyTransactionPrivate::getAttrName(static_cast(i)).isEmpty(); if (isEmpty) qWarning() << "Empty attribute's name " << i; QVERIFY(!isEmpty); } } diff --git a/kmymoney/mymoney/tests/onlinejob-test.cpp b/kmymoney/mymoney/tests/onlinejob-test.cpp index a72fb0147..d87c60e5d 100644 --- a/kmymoney/mymoney/tests/onlinejob-test.cpp +++ b/kmymoney/mymoney/tests/onlinejob-test.cpp @@ -1,102 +1,102 @@ /* * 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 "onlinejob-test.h" #include #define KMM_MYMONEY_UNIT_TESTABLE friend class onlineJobTest; #include "onlinejob.h" #include "onlinejob_p.h" #include "onlinetasks/dummy/tasks/dummytask.h" QTEST_GUILESS_MAIN(onlineJobTest) void onlineJobTest::testDefaultConstructor() { const onlineJob job = onlineJob(); - QVERIFY(job.id() == MyMoneyObject::emptyId()); + QCOMPARE(job.id(), QString()); QVERIFY(job.isNull()); QVERIFY(job.sendDate().isNull()); QVERIFY(job.bankAnswerDate().isNull()); QVERIFY(job.bankAnswerState() == onlineJob::noBankAnswer); QVERIFY(job.jobMessageList().isEmpty()); QVERIFY(job.isLocked() == false); } void onlineJobTest::testCopyConstructor() { onlineJob originalJob = onlineJob(new dummyTask, "O000001"); QVERIFY(!originalJob.isNull()); QVERIFY(originalJob.task()); onlineJob jobCopy = onlineJob(originalJob); QVERIFY(!jobCopy.isNull()); QCOMPARE(jobCopy.id(), QString("O000001")); QVERIFY(originalJob.task() != jobCopy.task()); } void onlineJobTest::testCopyAssignment() { onlineJob originalJob = onlineJob(new dummyTask, "O000001"); QVERIFY(!originalJob.isNull()); QVERIFY(originalJob.task()); onlineJob jobCopy; jobCopy = originalJob; QVERIFY(!jobCopy.isNull()); QCOMPARE(jobCopy.id(), QString("O000001")); QVERIFY(originalJob.task() != jobCopy.task()); } void onlineJobTest::testCopyConstructorWithNewId() { onlineJob originalJob = onlineJob(new dummyTask, "O000001"); originalJob.setBankAnswer(onlineJob::acceptedByBank); QVERIFY(!originalJob.isNull()); onlineJob jobCopy = onlineJob("O000002", originalJob); QVERIFY(!jobCopy.isNull()); QCOMPARE(jobCopy.id(), QString("O000002")); QVERIFY(originalJob.task() != jobCopy.task()); QVERIFY(jobCopy.bankAnswerDate().isNull()); } void onlineJobTest::testElementNames() { for (auto i = (int)OnlineJob::Element::OnlineTask; i <= (int)OnlineJob::Element::OnlineTask; ++i) { auto isEmpty = onlineJobPrivate::getElName(static_cast(i)).isEmpty(); if (isEmpty) qWarning() << "Empty element's name " << i; QVERIFY(!isEmpty); } } void onlineJobTest::testAttributeNames() { for (auto i = (int)OnlineJob::Attribute::Send; i < (int)OnlineJob::Attribute::LastAttribute; ++i) { auto isEmpty = onlineJobPrivate::getAttrName(static_cast(i)).isEmpty(); if (isEmpty) qWarning() << "Empty attribute's name " << i; QVERIFY(!isEmpty); } } diff --git a/kmymoney/widgets/registeritem.cpp b/kmymoney/widgets/registeritem.cpp index 71d61a3ed..36446c06c 100644 --- a/kmymoney/widgets/registeritem.cpp +++ b/kmymoney/widgets/registeritem.cpp @@ -1,300 +1,300 @@ /*************************************************************************** registeritem.cpp - description ------------------- begin : Tue Jun 13 2006 copyright : (C) 2000-2006 by Thomas Baumgart email : Thomas Baumgart (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. * * * ***************************************************************************/ #include "registeritem.h" #include "registeritem_p.h" // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "register.h" #include "kmymoneyglobalsettings.h" #include "mymoneyobject.h" #include "mymoneymoney.h" #include "mymoneyenums.h" #include "widgetenums.h" using namespace KMyMoneyRegister; QDate RegisterItemPrivate::nullDate; QString RegisterItemPrivate::nullString; MyMoneyMoney RegisterItemPrivate::nullValue; RegisterItem::RegisterItem(Register* parent) : d_ptr(new RegisterItemPrivate) { Q_D(RegisterItem); d->m_parent = parent; parent->addItem(this); } RegisterItem::RegisterItem(RegisterItemPrivate &dd, Register* parent) : d_ptr(&dd) { Q_D(RegisterItem); d->m_parent = parent; parent->addItem(this); } RegisterItem::~RegisterItem() { Q_D(RegisterItem); d->m_parent->removeItem(this); delete d; } bool RegisterItem::isSelected() const { return false; } void RegisterItem::setSelected(bool /* selected*/) { } bool RegisterItem::hasFocus() const { return false; } bool RegisterItem::hasEditorOpen() const { return false; } void RegisterItem::setFocus(bool /*focus*/, bool updateLens) { Q_UNUSED(updateLens); } QDate RegisterItem::sortPostDate() const { Q_D(const RegisterItem); return d->nullDate; } QDate RegisterItem::sortEntryDate() const { Q_D(const RegisterItem); return d->nullDate; } const QString& RegisterItem::sortPayee() const { Q_D(const RegisterItem); return d->nullString; } MyMoneyMoney RegisterItem::sortValue() const { Q_D(const RegisterItem); return d->nullValue; } QString RegisterItem::sortNumber() const { Q_D(const RegisterItem); return d->nullString; } const QString& RegisterItem::sortEntryOrder() const { Q_D(const RegisterItem); return d->nullString; } eWidgets::eRegister::CashFlowDirection RegisterItem::sortType() const { return eWidgets::eRegister::CashFlowDirection::Deposit; } const QString& RegisterItem::sortCategory() const { Q_D(const RegisterItem); return d->nullString; } eMyMoney::Split::State RegisterItem::sortReconcileState() const { return eMyMoney::Split::State::MaxReconcileState; } const QString RegisterItem::sortSecurity() const { Q_D(const RegisterItem); return d->nullString; } void RegisterItem::setStartRow(int row) { Q_D(RegisterItem); d->m_startRow = row; } int RegisterItem::startRow() const { Q_D(const RegisterItem); return d->m_startRow; } -const QString& RegisterItem::id() const +QString RegisterItem::id() const { - return MyMoneyObject::emptyId(); + return QString(); } void RegisterItem::setParent(Register* parent) { Q_D(RegisterItem); d->m_parent = parent; } Register* RegisterItem::getParent() const { Q_D(const RegisterItem); return d->m_parent; } void RegisterItem::setNeedResize() { Q_D(RegisterItem); d->m_needResize = true; } bool RegisterItem::isVisible() const { Q_D(const RegisterItem); return d->m_visible; } void RegisterItem::setNumRowsRegister(int rows) { Q_D(RegisterItem); if (rows != d->m_rowsRegister) { d->m_rowsRegister = rows; if (d->m_parent) d->m_parent->forceUpdateLists(); } } void RegisterItem::setNumRowsForm(int rows) { Q_D(RegisterItem); d->m_rowsForm = rows; } int RegisterItem::numRowsRegister() const { Q_D(const RegisterItem); return d->m_rowsRegister; } int RegisterItem::numRowsForm() const { Q_D(const RegisterItem); return d->m_rowsForm; } int RegisterItem::numColsForm() const { return 1; } void RegisterItem::setAlternate(bool alternate) { Q_D(RegisterItem); d->m_alternate = alternate; } bool RegisterItem::markVisible(bool visible) { Q_D(RegisterItem); if (d->m_visible == visible) return false; d->m_visible = visible; return true; } void RegisterItem::setNextItem(RegisterItem* p) { Q_D(RegisterItem); d->m_next = p; } void RegisterItem::setPrevItem(RegisterItem* p) { Q_D(RegisterItem); d->m_prev = p; } RegisterItem* RegisterItem::nextItem() const { Q_D(const RegisterItem); return d->m_next; } RegisterItem* RegisterItem::prevItem() const { Q_D(const RegisterItem); return d->m_prev; } bool RegisterItem::maybeTip(const QPoint& /* relpos */, int /* row */, int /* col */, QRect& /* r */, QString& /* msg */) { return false; } void RegisterItem::setVisible(bool visible) { Q_D(RegisterItem); if (markVisible(visible) && d->m_parent) { int numRows = d->m_parent->rowCount(); if (visible) { for (int i = startRow(); i < startRow() + numRowsRegister(); ++i) { if (numRows > i) { d->m_parent->showRow(i); d->m_parent->setRowHeight(i, rowHeightHint()); } } } else { for (int i = startRow(); i < startRow() + numRowsRegister(); ++i) { if (numRows > i) { d->m_parent->hideRow(i); } } } } } int RegisterItem::rowHeightHint() const { Q_D(const RegisterItem); if (!d->m_visible) return 0; if (d->m_parent) { return d->m_parent->rowHeightHint(); } QFontMetrics fm(KMyMoneyGlobalSettings::listCellFont()); return fm.lineSpacing() + 6; } diff --git a/kmymoney/widgets/registeritem.h b/kmymoney/widgets/registeritem.h index d1feb5cf8..1d5cedeb0 100644 --- a/kmymoney/widgets/registeritem.h +++ b/kmymoney/widgets/registeritem.h @@ -1,202 +1,202 @@ /*************************************************************************** registeritem.h - description ------------------- begin : Tue Jun 13 2006 copyright : (C) 2000-2006 by Thomas Baumgart email : Thomas Baumgart (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 REGISTERITEM_H #define REGISTERITEM_H // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes class QDate; class QPainter; class QPoint; class QRect; class QStyleOptionViewItem; class QModelIndex; class MyMoneyMoney; namespace eMyMoney { namespace Split { enum class State; } } namespace eWidgets { namespace eRegister { enum class CashFlowDirection; } } namespace KMyMoneyRegister { struct RegisterFilter; class Register; /** * @author Thomas Baumgart */ class RegisterItemPrivate; class RegisterItem { Q_DISABLE_COPY(RegisterItem) public: explicit RegisterItem(Register* getParent); virtual ~RegisterItem(); virtual const char* className() = 0; virtual bool isSelectable() const = 0; virtual bool isSelected() const; virtual void setSelected(bool /* selected*/); virtual bool canHaveFocus() const = 0; virtual bool hasFocus() const; virtual bool hasEditorOpen() const; virtual void setFocus(bool /*focus*/, bool updateLens = true); virtual bool isErroneous() const = 0; // helper functions used for sorting virtual QDate sortPostDate() const; virtual int sortSamePostDate() const = 0; virtual QDate sortEntryDate() const; virtual const QString& sortPayee() const; virtual MyMoneyMoney sortValue() const; virtual QString sortNumber() const; virtual const QString& sortEntryOrder() const; virtual eWidgets::eRegister::CashFlowDirection sortType() const; virtual const QString& sortCategory() const; virtual eMyMoney::Split::State sortReconcileState() const; virtual const QString sortSecurity() const; /** * This method sets the row offset of the item in the register * to row. * * @param row row offset * * @note The row offset is based on QTable rows, not register * items. */ virtual void setStartRow(int row); int startRow() const; virtual int rowHeightHint() const; /** * This method modifies the number of rows required to display this item * in a Register. * It calls Register::forceUpdateLists() when the number differs. */ virtual void setNumRowsRegister(int rows); /** * This method modifies the number of rows required to display this item * in a Form. */ virtual void setNumRowsForm(int rows); /** * This method returns the number of rows required to display this item * in a Register */ virtual int numRowsRegister() const; /** * This method returns the number of rows required to display this item * in a Form */ virtual int numRowsForm() const; virtual int numColsForm() const; /** * This method sets up the register item to be shown in normal (@p alternate = @p false) * or alternate (@p alternate = @p true) background. * * @param alternate selects normal or alternate background */ virtual void setAlternate(bool alternate); virtual void paintRegisterCell(QPainter *painter, QStyleOptionViewItem &option, const QModelIndex &index) = 0; virtual void paintFormCell(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) = 0; - virtual const QString& id() const; + virtual QString id() const; /** * Sets the parent of this item to be the register @p parent * * @param parent pointer to register */ void setParent(Register* getParent); /** * This member returns a pointer to the parent object * * @retval pointer to Register */ Register* getParent() const; void setNeedResize(); bool isVisible() const; /** * Marks the item visible depending on @a visible and * updates the underlying register object */ virtual void setVisible(bool visible); /** * Marks the item visible depending on @a visible but * does not update the underlying register object. Returns * true, if visibility has changed. */ virtual bool markVisible(bool visible); void setNextItem(RegisterItem* p); void setPrevItem(RegisterItem* p); RegisterItem* nextItem() const; RegisterItem* prevItem() const; virtual bool matches(const RegisterFilter&) const = 0; /** * Checks if the mouse hovered over an area that has a tooltip associated with it. * The mouse position is given in relative coordinates to the @a startRow and the * @a row and @a col of the item are also passed as relative values. * * If a tooltip shall be shown, this method presets the rectangle @a r with the * area in register coordinates and @a msg with the string that will be passed * to QToolTip::tip. @a true is returned in this case. * * If no tooltip is available, @a false will be returned. */ virtual bool maybeTip(const QPoint& /* relpos */, int /* row */, int /* col */, QRect& /* r */, QString& /* msg */); protected: RegisterItemPrivate * const d_ptr; RegisterItem(RegisterItemPrivate &dd, Register *parent); private: Q_DECLARE_PRIVATE(RegisterItem) }; } // namespace #endif diff --git a/kmymoney/widgets/transaction.cpp b/kmymoney/widgets/transaction.cpp index 322a586f4..01c5fac3a 100644 --- a/kmymoney/widgets/transaction.cpp +++ b/kmymoney/widgets/transaction.cpp @@ -1,933 +1,933 @@ /*************************************************************************** transaction.cpp - description ------------------- begin : Tue Jun 13 2006 copyright : (C) 2000-2006 by Thomas Baumgart (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. * * * ***************************************************************************/ #include "transaction.h" #include "transaction_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyutils.h" #include "mymoneytransaction.h" #include "mymoneysplit.h" #include "mymoneyfile.h" #include "mymoneysecurity.h" #include "mymoneypayee.h" #include "mymoneytag.h" #include "register.h" #include "kmymoneycategory.h" #include "kmymoneydateinput.h" #include "transactionform.h" #include "kmymoneyedit.h" #include "kmymoneyutils.h" #include "registerfilter.h" #include "tabbar.h" #include "kmymoneyglobalsettings.h" #include "widgetenums.h" #include "mymoneyenums.h" using namespace eWidgets; using namespace KMyMoneyRegister; using namespace KMyMoneyTransactionForm; static unsigned char attentionSign[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x08, 0x06, 0x00, 0x00, 0x00, 0x8D, 0x89, 0x1D, 0x0D, 0x00, 0x00, 0x00, 0x04, 0x73, 0x42, 0x49, 0x54, 0x08, 0x08, 0x08, 0x08, 0x7C, 0x08, 0x64, 0x88, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x77, 0x77, 0x77, 0x2E, 0x69, 0x6E, 0x6B, 0x73, 0x63, 0x61, 0x70, 0x65, 0x2E, 0x6F, 0x72, 0x67, 0x9B, 0xEE, 0x3C, 0x1A, 0x00, 0x00, 0x02, 0x05, 0x49, 0x44, 0x41, 0x54, 0x38, 0x8D, 0xAD, 0x95, 0xBF, 0x4B, 0x5B, 0x51, 0x14, 0xC7, 0x3F, 0x2F, 0xBC, 0x97, 0x97, 0x97, 0x97, 0x77, 0xF3, 0xF2, 0x1C, 0xA4, 0x54, 0x6B, 0x70, 0x10, 0x44, 0x70, 0x2A, 0x91, 0x2E, 0x52, 0x02, 0x55, 0x8A, 0xB5, 0xA3, 0xAB, 0x38, 0x08, 0x66, 0xCC, 0xEE, 0xE0, 0xE2, 0x20, 0xB8, 0x38, 0xB8, 0xB8, 0xF8, 0x1F, 0x38, 0x29, 0xA5, 0x29, 0x74, 0x90, 0x0E, 0x0D, 0x0E, 0x22, 0x1D, 0x44, 0xA8, 0xD0, 0xD4, 0xB4, 0x58, 0x4B, 0x09, 0xF9, 0xF1, 0x4A, 0x3B, 0xD4, 0xD3, 0xE1, 0x55, 0xD3, 0x34, 0xAF, 0x49, 0x6C, 0x3D, 0xF0, 0x85, 0x7B, 0xCF, 0xFD, 0x9E, 0xEF, 0x3D, 0xE7, 0xFE, 0xD4, 0x44, 0x84, 0xDB, 0xB4, 0x48, 0x2F, 0xA4, 0x94, 0xAB, 0xE5, 0x52, 0xAE, 0x96, 0xEB, 0x49, 0x51, 0x44, 0x3A, 0x02, 0x18, 0x88, 0xC7, 0xF1, 0xE3, 0x71, 0x7C, 0x60, 0xA0, 0x1B, 0xBF, 0x6B, 0x86, 0x49, 0xC5, 0x46, 0x3E, 0x47, 0x34, 0x9F, 0x23, 0x9A, 0x54, 0x6C, 0xFC, 0x57, 0x86, 0x40, 0xC6, 0x4B, 0xE1, 0x37, 0xCA, 0x48, 0xA3, 0x8C, 0x78, 0x29, 0x7C, 0x20, 0xD3, 0x31, 0xA6, 0xD3, 0xA0, 0x52, 0x1C, 0x6D, 0x6F, 0x72, 0xD9, 0x28, 0x23, 0xFE, 0x07, 0x64, 0x7B, 0x93, 0x4B, 0xA5, 0x38, 0xFA, 0x27, 0x41, 0x60, 0x6E, 0x74, 0x84, 0x7A, 0xE5, 0x1D, 0x92, 0x54, 0x88, 0xE7, 0x22, 0xD5, 0x12, 0x32, 0x3A, 0x42, 0x1D, 0x98, 0xBB, 0x91, 0x20, 0x60, 0xDA, 0x36, 0x17, 0xFB, 0x7B, 0xC8, 0xC1, 0x4B, 0x04, 0x02, 0xBC, 0x7E, 0x81, 0xEC, 0xEF, 0x21, 0xB6, 0xCD, 0x05, 0x60, 0xF6, 0x2C, 0x68, 0x9A, 0x2C, 0xCF, 0x4C, 0xE1, 0x4B, 0x05, 0x39, 0x3F, 0x69, 0x0A, 0xBE, 0x7F, 0x83, 0x48, 0x05, 0x99, 0x99, 0xC2, 0x37, 0x4D, 0x96, 0x7B, 0x12, 0x04, 0xFA, 0x2D, 0x8B, 0xC6, 0xE9, 0x61, 0x10, 0x2C, 0x15, 0xC4, 0x8A, 0x21, 0x86, 0x8E, 0xFC, 0xF8, 0x12, 0xF4, 0x4F, 0x0F, 0x11, 0xCB, 0xA2, 0x01, 0xF4, 0x77, 0x3D, 0x36, 0x4E, 0x82, 0xF5, 0xA5, 0x05, 0x8C, 0xE1, 0x74, 0xD3, 0x37, 0x34, 0x18, 0x20, 0xF2, 0x8B, 0x3D, 0x9C, 0x86, 0xA5, 0x05, 0x0C, 0x27, 0xC1, 0x7A, 0xC7, 0x63, 0x03, 0x8C, 0x2B, 0x07, 0xBF, 0x5A, 0x6A, 0x66, 0x27, 0x15, 0x64, 0x3A, 0x8B, 0x3C, 0x7A, 0xD8, 0xEA, 0xAB, 0x96, 0x10, 0xE5, 0xE0, 0x03, 0xE3, 0x7F, 0xCD, 0x50, 0x39, 0x6C, 0xAD, 0xAD, 0x10, 0x53, 0xAA, 0x75, 0xD2, 0xF4, 0xBD, 0x00, 0x2D, 0x5C, 0x05, 0x6B, 0x2B, 0xC4, 0x94, 0xC3, 0xD6, 0xEF, 0xFE, 0x6B, 0x41, 0x4D, 0xD3, 0x66, 0xFB, 0x3C, 0xC6, 0x16, 0xE7, 0xDB, 0x97, 0x61, 0xE2, 0x3E, 0x3C, 0xC8, 0xB4, 0x15, 0xC7, 0xE2, 0x3C, 0x91, 0x3E, 0x8F, 0x31, 0x4D, 0xD3, 0x66, 0x5B, 0x4A, 0x06, 0x8C, 0x84, 0xCD, 0x59, 0x61, 0xA7, 0xB5, 0xAC, 0x2B, 0x9C, 0x1C, 0x04, 0x08, 0x1B, 0x2B, 0xEC, 0x20, 0x09, 0x9B, 0x33, 0xC0, 0xB8, 0xDE, 0x65, 0x43, 0x27, 0x9F, 0x9D, 0xA4, 0x1E, 0x16, 0xF0, 0xF9, 0x6D, 0xB0, 0xC3, 0x86, 0x1E, 0xB4, 0xC3, 0x38, 0xD9, 0x49, 0xEA, 0x86, 0x4E, 0xFE, 0xEA, 0x29, 0xF4, 0x2C, 0x8B, 0xDA, 0x71, 0x31, 0x9C, 0xFC, 0xF5, 0x23, 0x32, 0x34, 0x88, 0xDC, 0xBD, 0x13, 0x5C, 0xBF, 0x30, 0xCE, 0x71, 0x11, 0xB1, 0x2C, 0x6A, 0x80, 0xA7, 0xDB, 0x36, 0xAB, 0x4F, 0xA6, 0x89, 0xBA, 0x49, 0x38, 0xFF, 0xD4, 0xBE, 0x4E, 0x00, 0xAF, 0x9E, 0x81, 0x08, 0xD4, 0xEA, 0x01, 0xFE, 0x34, 0x37, 0x09, 0x4F, 0x1F, 0x13, 0xDD, 0x7D, 0xCE, 0xAA, 0x96, 0x72, 0x29, 0x7C, 0xFB, 0xCE, 0x44, 0xB8, 0xD4, 0xCD, 0x2C, 0x66, 0x52, 0xD4, 0x6E, 0xFB, 0x0B, 0xF8, 0x09, 0x63, 0x63, 0x31, 0xE4, 0x85, 0x76, 0x2E, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; Transaction::Transaction(Register *parent, const MyMoneyTransaction& transaction, const MyMoneySplit& split, int uniqueId) : RegisterItem(*new TransactionPrivate, parent) { Q_D(Transaction); d->m_transaction = transaction; d->m_split = split; d->m_form = nullptr; d->m_uniqueId = d->m_transaction.id(); d->init(uniqueId); } Transaction::Transaction(TransactionPrivate &dd, Register* parent, const MyMoneyTransaction& transaction, const MyMoneySplit& split, int uniqueId) : RegisterItem(dd, parent), d_ptr(&dd) { Q_D(Transaction); d->m_form = nullptr; d->m_transaction = transaction; d->m_split = split; d->m_uniqueId = d->m_transaction.id(); d->init(uniqueId); } Transaction::~Transaction() { } const char* Transaction::className() { return "Transaction"; } bool Transaction::isSelectable() const { return true; } bool Transaction::isSelected() const { Q_D(const Transaction); return d->m_selected; } void Transaction::setSelected(bool selected) { Q_D(Transaction); if (!selected || (selected && isVisible())) d->m_selected = selected; } bool Transaction::canHaveFocus() const { return true; } bool Transaction::hasFocus() const { Q_D(const Transaction); return d->m_focus; } bool Transaction::hasEditorOpen() const { Q_D(const Transaction); return d->m_inEdit; } bool Transaction::isScheduled() const { return false; } void Transaction::setFocus(bool focus, bool updateLens) { Q_D(Transaction); if (focus != d->m_focus) { d->m_focus = focus; } if (updateLens) { if (KMyMoneyGlobalSettings::ledgerLens() || !KMyMoneyGlobalSettings::transactionForm() || KMyMoneyGlobalSettings::showRegisterDetailed() || d->m_parent->ledgerLens()) { if (focus) setNumRowsRegister(numRowsRegister(true)); else setNumRowsRegister(numRowsRegister(KMyMoneyGlobalSettings::showRegisterDetailed())); } } } bool Transaction::isErroneous() const { Q_D(const Transaction); return d->m_erroneous; } QDate Transaction::sortPostDate() const { Q_D(const Transaction); return d->m_transaction.postDate(); } int Transaction::sortSamePostDate() const { return 2; } QDate Transaction::sortEntryDate() const { Q_D(const Transaction); return d->m_transaction.entryDate(); } const QString& Transaction::sortPayee() const { Q_D(const Transaction); return d->m_payee; } const QList& Transaction::sortTagList() const { Q_D(const Transaction); return d->m_tagList; } MyMoneyMoney Transaction::sortValue() const { Q_D(const Transaction); return d->m_split.shares(); } QString Transaction::sortNumber() const { Q_D(const Transaction); return d->m_split.number(); } const QString& Transaction::sortEntryOrder() const { Q_D(const Transaction); return d->m_uniqueId; } eRegister::CashFlowDirection Transaction::sortType() const { Q_D(const Transaction); return d->m_split.shares().isNegative() ? eRegister::CashFlowDirection::Payment : eRegister::CashFlowDirection::Deposit; } const QString& Transaction::sortCategory() const { Q_D(const Transaction); return d->m_category; } eMyMoney::Split::State Transaction::sortReconcileState() const { Q_D(const Transaction); return d->m_split.reconcileFlag(); } -const QString& Transaction::id() const +QString Transaction::id() const { Q_D(const Transaction); return d->m_uniqueId; } const MyMoneyTransaction& Transaction::transaction() const { Q_D(const Transaction); return d->m_transaction; } const MyMoneySplit& Transaction::split() const { Q_D(const Transaction); return d->m_split; } void Transaction::setBalance(const MyMoneyMoney& balance) { Q_D(Transaction); d->m_balance = balance; } const MyMoneyMoney& Transaction::balance() const { Q_D(const Transaction); return d->m_balance; } bool Transaction::paintRegisterCellSetup(QPainter *painter, QStyleOptionViewItem &option, const QModelIndex &index) { Q_D(Transaction); Q_UNUSED(painter) if (d->m_reducedIntensity) { option.palette.setColor(QPalette::Text, option.palette.color(QPalette::Disabled, QPalette::Text)); } if (d->m_selected) { option.state |= QStyle::State_Selected; } else { option.state &= ~QStyle::State_Selected; } if (d->m_focus) { option.state |= QStyle::State_HasFocus; } else { option.state &= ~QStyle::State_HasFocus; } if (option.widget && option.widget->hasFocus()) { option.palette.setCurrentColorGroup(QPalette::Active); } else { option.palette.setCurrentColorGroup(QPalette::Inactive); } if (index.column() == 0) { option.viewItemPosition = QStyleOptionViewItem::Beginning; } else if (index.column() == (int)eTransaction::Column::LastColumn - 1) { option.viewItemPosition = QStyleOptionViewItem::End; } else { option.viewItemPosition = QStyleOptionViewItem::Middle; } // do we need to switch to the error color? if (d->m_erroneous) { option.palette.setColor(QPalette::Text, KMyMoneyGlobalSettings::schemeColor(SchemeColor::TransactionErroneous)); } // do we need to switch to the negative balance color? if (index.column() == (int)eTransaction::Column::Balance) { bool showNegative = d->m_balance.isNegative(); if (d->m_account.accountGroup() == eMyMoney::Account::Type::Liability && !d->m_balance.isZero()) showNegative = !showNegative; if (showNegative) option.palette.setColor(QPalette::Text, KMyMoneyGlobalSettings::schemeColor(SchemeColor::TransactionErroneous)); } return true; } void Transaction::registerCellText(QString& txt, int row, int col) { Qt::Alignment align; registerCellText(txt, align, row, col, 0); } void Transaction::paintRegisterCell(QPainter *painter, QStyleOptionViewItem &option, const QModelIndex &index) { Q_D(Transaction); painter->save(); if (paintRegisterCellSetup(painter, option, index)) { const QStyle *style = option.widget ? option.widget->style() : QApplication::style(); const QWidget* widget = option.widget; // clear the mouse over state before painting the background option.state &= ~QStyle::State_MouseOver; // the background if (option.state & QStyle::State_Selected || option.state & QStyle::State_HasFocus) { // if this is not the first row of the transaction paint the previous rows // since the selection background is painted from the first row of the transaction if (index.row() > startRow()) { QStyleOptionViewItem optionSibling = option; QModelIndex previousRowItem = index.sibling(index.row() - 1, index.column()); optionSibling.rect = d->m_parent->visualRect(previousRowItem); paintRegisterCell(painter, optionSibling, previousRowItem); } // paint the selection background only from the first row on to the last row at once if (index.row() == startRow()) { QRect old = option.rect; int extraHeight = 0; if (d->m_inRegisterEdit) { // since, when editing a transaction inside the register (without the transaction form), // row heights can have various sizes (the memo row is larger than the rest) we have // to iterate over all the items of the transaction to compute the size of the selection rectangle // of course we start with the item after this one because it's size is already in the rectangle for (int i = startRow() + 1; i < startRow() + numRowsRegister(); ++i) { extraHeight += d->m_parent->visualRect(index.sibling(i, index.column())).height(); } } else { // we are not editing in the register so all rows have the same sizes just compute the extra height extraHeight = (numRowsRegister() - 1) * option.rect.height(); } option.rect.setBottom(option.rect.bottom() + extraHeight); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, widget); if (d->m_focus && index.column() == (int)eTransaction::Column::Detail) { option.state |= QStyle::State_HasFocus; style->drawPrimitive(QStyle::PE_FrameFocusRect, &option, painter, widget); } option.rect = old; } } else { if (d->m_alternate) { painter->fillRect(option.rect, option.palette.alternateBase()); } else { painter->fillRect(option.rect, option.palette.base()); } } // the text // construct the text for the cell QString txt; option.displayAlignment = Qt::AlignVCenter; if (d->m_transaction != MyMoneyTransaction() && !d->m_inRegisterEdit) { registerCellText(txt, option.displayAlignment, index.row() - startRow(), index.column(), painter); } if (Qt::mightBeRichText(txt)) { QTextDocument document; // this should set the alignment of the html but it does not work so htmls will be left aligned document.setDefaultTextOption(QTextOption(option.displayAlignment)); document.setDocumentMargin(2); document.setHtml(txt); painter->translate(option.rect.topLeft()); QAbstractTextDocumentLayout::PaintContext ctx; ctx.palette = option.palette; // Highlighting text if item is selected if (d->m_selected) ctx.palette.setColor(QPalette::Text, option.palette.color(QPalette::HighlightedText)); document.documentLayout()->draw(painter, ctx); painter->translate(-option.rect.topLeft()); } else { // draw plain text properly aligned style->drawItemText(painter, option.rect.adjusted(2, 0, -2, 0), option.displayAlignment, option.palette, true, txt, d->m_selected ? QPalette::HighlightedText : QPalette::Text); } // draw the grid if it's needed if (KMyMoneySettings::showGrid()) { const int gridHint = style->styleHint(QStyle::SH_Table_GridLineColor, &option, widget); const QPen gridPen = QPen(QColor(static_cast(gridHint)), 0); QPen old = painter->pen(); painter->setPen(gridPen); if (index.row() == startRow()) painter->drawLine(option.rect.topLeft(), option.rect.topRight()); painter->drawLine(option.rect.topLeft(), option.rect.bottomLeft()); painter->setPen(old); } // possible icons if (index.row() == startRow() && index.column() == (int)eTransaction::Column::Detail) { if (d->m_erroneous) { QPixmap attention; attention.loadFromData(attentionSign, sizeof(attentionSign), 0, 0); style->drawItemPixmap(painter, option.rect, Qt::AlignRight | Qt::AlignVCenter, attention); } } } painter->restore(); } bool Transaction::formCellText(QString& /* txt */, Qt::Alignment& /* align */, int /* row */, int /* col */, QPainter* /* painter */) { return false; } void Transaction::registerCellText(QString& /* txt */, Qt::Alignment& /* align */, int /* row */, int /* col */, QPainter* /* painter */) { } int Transaction::registerColWidth(int /* col */, const QFontMetrics& /* cellFontMetrics */) { return 0; } int Transaction::formRowHeight(int /*row*/) { Q_D(Transaction); if (d->m_formRowHeight < 0) { d->m_formRowHeight = formRowHeight(); } return d->m_formRowHeight; } int Transaction::formRowHeight() const { Q_D(const Transaction); if (d->m_formRowHeight < 0) { // determine the height of the objects in the table KMyMoneyDateInput dateInput; KMyMoneyCategory category(true, nullptr); return qMax(dateInput.sizeHint().height(), category.sizeHint().height()); } return d->m_formRowHeight; } void Transaction::setupForm(TransactionForm* form) { Q_D(Transaction); d->m_form = form; form->verticalHeader()->setUpdatesEnabled(false); form->horizontalHeader()->setUpdatesEnabled(false); form->setRowCount(numRowsForm()); form->setColumnCount(numColsForm()); // Force all cells to have some text (so that paintCell is called for each cell) for (int r = 0; r < numRowsForm(); ++r) { for (int c = 0; c < numColsForm(); ++c) { if (r == 0 && form->columnWidth(c) == 0) { form->setColumnWidth(c, 10); } } } form->horizontalHeader()->setUpdatesEnabled(true); form->verticalHeader()->setUpdatesEnabled(true); loadTab(form); } void Transaction::paintFormCell(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) { Q_D(Transaction); if (!d->m_form) return; QRect cellRect = option.rect; QRect textRect(cellRect); textRect.setWidth(textRect.width() - 2); textRect.setHeight(textRect.height() - 2); painter->setPen(option.palette.text().color()); QString txt; Qt::Alignment align = Qt::AlignVCenter; bool editField = formCellText(txt, align, index.row(), index.column(), painter); // if we have an editable field and don't currently edit the transaction // show the background in a different color if (editField && !d->m_inEdit) { painter->fillRect(textRect, option.palette.alternateBase()); } if (!d->m_inEdit) painter->drawText(textRect, align, txt); } void Transaction::setupPalette(const QPalette& palette, QMap& editWidgets) { QMap::iterator it_w; for (it_w = editWidgets.begin(); it_w != editWidgets.end(); ++it_w) { if (*it_w) { (*it_w)->setPalette(palette); } } } void Transaction::setupFormPalette(QMap& editWidgets) { Q_D(Transaction); QPalette palette = d->m_parent->palette(); palette.setColor(QPalette::Active, QPalette::Base, palette.color(QPalette::Active, QPalette::Base)); setupPalette(palette, editWidgets); } void Transaction::setupRegisterPalette(QMap& editWidgets) { Q_D(Transaction); // make sure, we're using the right palette QPalette palette = d->m_parent->palette(); // use the highlight color as background palette.setColor(QPalette::Active, QPalette::Background, palette.color(QPalette::Active, QPalette::Highlight)); setupPalette(palette, editWidgets); } QWidget* Transaction::focusWidget(QWidget* w) const { if (w) { while (w->focusProxy()) w = w->focusProxy(); } return w; } void Transaction::arrangeWidget(QTableWidget* tbl, int row, int col, QWidget* w) const { if (w) { tbl->setCellWidget(row, col, w); // remove the widget from the QTable's eventFilter so that all // events will be directed to the edit widget w->removeEventFilter(tbl); } } bool Transaction::haveNumberField() const { Q_D(const Transaction); auto rc = true; switch (d->m_account.accountType()) { case eMyMoney::Account::Type::Savings: case eMyMoney::Account::Type::Cash: case eMyMoney::Account::Type::Loan: case eMyMoney::Account::Type::AssetLoan: case eMyMoney::Account::Type::Asset: case eMyMoney::Account::Type::Liability: case eMyMoney::Account::Type::Equity: rc = KMyMoneyGlobalSettings::alwaysShowNrField(); break; case eMyMoney::Account::Type::Checkings: case eMyMoney::Account::Type::CreditCard: // the next case is used for the editor when the account // is unknown (eg. when creating new schedules) case eMyMoney::Account::Type::Unknown: break; default: rc = false; break; } return rc; } bool Transaction::maybeTip(const QPoint& cpos, int row, int col, QRect& r, QString& msg) { Q_D(Transaction); if (col != (int)eTransaction::Column::Detail) return false; if (!d->m_erroneous && d->m_transaction.splitCount() < 3) return false; // check for detail column in row 0 of the transaction for a possible // exclamation mark. m_startRow is based 0, whereas the row to obtain // the modelindex is based 1, so we need to add one here r = d->m_parent->visualRect(d->m_parent->model()->index(d->m_startRow + 1, col)); r.setBottom(r.bottom() + (numRowsRegister() - 1)*r.height()); if (r.contains(cpos) && d->m_erroneous) { if (d->m_transaction.splits().count() < 2) { msg = QString("%1").arg(i18n("Transaction is missing a category assignment.")); } else { const auto sec = MyMoneyFile::instance()->security(d->m_account.currencyId()); msg = QString("%1").arg(i18n("The transaction has a missing assignment of %1.", MyMoneyUtils::formatMoney(d->m_transaction.splitSum().abs(), d->m_account, sec))); } return true; } // check if the mouse cursor is located on row 1 of the transaction // and display the details of a split transaction if it is one if (row == 1 && r.contains(cpos) && d->m_transaction.splitCount() > 2) { auto file = MyMoneyFile::instance(); QList::const_iterator it_s; QString txt; const auto sec = file->security(d->m_transaction.commodity()); MyMoneyMoney factor(1, 1); if (!d->m_split.value().isNegative()) factor = -factor; for (it_s = d->m_transaction.splits().constBegin(); it_s != d->m_transaction.splits().constEnd(); ++it_s) { if (*it_s == d->m_split) continue; const MyMoneyAccount& acc = file->account((*it_s).accountId()); QString category = file->accountToCategory(acc.id()); QString amount = MyMoneyUtils::formatMoney(((*it_s).value() * factor), acc, sec); txt += QString("%1%2").arg(category, amount); } msg = QString("%1
").arg(txt); return true; } return false; } QString Transaction::reconcileState(bool text) const { Q_D(const Transaction); auto txt = KMyMoneyUtils::reconcileStateToString(d->m_split.reconcileFlag(), text); if ((text == true) && (txt == i18nc("Unknown reconciliation state", "Unknown")) && (d->m_transaction == MyMoneyTransaction())) txt.clear(); return txt; } void Transaction::startEditMode() { Q_D(Transaction); d->m_inEdit = true; // hide the original tabbar since the edit tabbar will be added KMyMoneyTransactionForm::TransactionForm* form = dynamic_cast(d->m_form); form->getTabBar()->setVisible(false); // only update the number of lines displayed if we edit inside the register if (d->m_inRegisterEdit) setNumRowsRegister(numRowsRegister(true)); } int Transaction::numRowsRegister() const { return RegisterItem::numRowsRegister(); } void Transaction::leaveEditMode() { Q_D(Transaction); // show the original tabbar since the edit tabbar was removed KMyMoneyTransactionForm::TransactionForm* form = dynamic_cast(d->m_form); form->getTabBar()->setVisible(true); // make sure we reset the row height of all the transaction's rows because it could have been changed during edit if (d->m_parent) { for (auto i = 0; i < numRowsRegister(); ++i) d->m_parent->setRowHeight(d->m_startRow + i, d->m_parent->rowHeightHint()); } d->m_inEdit = false; d->m_inRegisterEdit = false; setFocus(hasFocus(), true); } void Transaction::singleLineMemo(QString& txt, const MyMoneySplit& split) const { txt = split.memo(); // remove empty lines txt.replace("\n\n", "\n"); // replace '\n' with ", " txt.replace('\n', ", "); } int Transaction::rowHeightHint() const { Q_D(const Transaction); return d->m_inEdit ? formRowHeight() : RegisterItem::rowHeightHint(); } bool Transaction::matches(const RegisterFilter& filter) const { Q_D(const Transaction); // check if the state matches if (!transaction().id().isEmpty()) { switch (filter.state) { default: break; case eRegister::ItemState::Imported: if (!transaction().isImported()) return false; break; case eRegister::ItemState::Matched: if (!split().isMatched()) return false; break; case eRegister::ItemState::Erroneous: if (transaction().splitSum().isZero()) return false; break; case eRegister::ItemState::NotMarked: if (split().reconcileFlag() != eMyMoney::Split::State::NotReconciled) return false; break; case eRegister::ItemState::NotReconciled: if (split().reconcileFlag() != eMyMoney::Split::State::NotReconciled && split().reconcileFlag() != eMyMoney::Split::State::Cleared) return false; break; case eRegister::ItemState::Cleared: if (split().reconcileFlag() != eMyMoney::Split::State::Cleared) return false; break; } } // check if the text matches if (filter.text.isEmpty() || d->m_transaction.splitCount() == 0) return true; auto file = MyMoneyFile::instance(); const QList&list = d->m_transaction.splits(); QList::const_iterator it_s; for (it_s = list.begin(); it_s != list.end(); ++it_s) { // check if the text is contained in one of the fields // memo, number, payee, tag, account if ((*it_s).memo().contains(filter.text, Qt::CaseInsensitive) || (*it_s).number().contains(filter.text, Qt::CaseInsensitive)) return true; if (!(*it_s).payeeId().isEmpty()) { const MyMoneyPayee& payee = file->payee((*it_s).payeeId()); if (payee.name().contains(filter.text, Qt::CaseInsensitive)) return true; } if (!(*it_s).tagIdList().isEmpty()) { const QList& t = (*it_s).tagIdList(); for (auto i = 0; i < t.count(); i++) { if ((file->tag(t[i])).name().contains(filter.text, Qt::CaseInsensitive)) return true; } } const MyMoneyAccount& acc = file->account((*it_s).accountId()); if (acc.name().contains(filter.text, Qt::CaseInsensitive)) return true; QString s(filter.text); s.replace(MyMoneyMoney::thousandSeparator(), QChar()); if (!s.isEmpty()) { // check if any of the value field matches if a value has been entered QString r = (*it_s).value().formatMoney(d->m_account.fraction(), false); if (r.contains(s, Qt::CaseInsensitive)) return true; const MyMoneyAccount& acc = file->account((*it_s).accountId()); r = (*it_s).shares().formatMoney(acc.fraction(), false); if (r.contains(s, Qt::CaseInsensitive)) return true; } } return false; } void Transaction::setShowBalance(bool showBalance) { Q_D(Transaction); d->m_showBalance = showBalance; } bool Transaction::showRowInForm(int row) const { Q_UNUSED(row) return true; } void Transaction::setShowRowInForm(int row, bool show) { Q_UNUSED(row); Q_UNUSED(show) } void Transaction::setReducedIntensity(bool reduced) { Q_D(Transaction); d->m_reducedIntensity = reduced; } void Transaction::setVisible(bool visible) { Q_D(Transaction); if (visible != isVisible()) { RegisterItem::setVisible(visible); RegisterItem* p; Transaction* t; if (!visible) { // if we are hidden, we need to inform all previous transactions // about it so that they don't show the balance p = prevItem(); while (p) { t = dynamic_cast(p); if (t) { if (!t->d_func()->m_showBalance) break; t->d_func()->m_showBalance = false; } p = p->prevItem(); } } else { // if we are shown, we need to check if the next transaction // is visible and change the display of the balance p = this; do { p = p->nextItem(); t = dynamic_cast(p); } while (!t && p); // if the next transaction is visible or I am the last one if ((t && t->d_func()->m_showBalance) || !t) { d->m_showBalance = true; p = prevItem(); while (p && p->isVisible()) { t = dynamic_cast(p); if (t) { if (t->d_func()->m_showBalance) break; t->d_func()->m_showBalance = true; } p = p->prevItem(); } } } } } diff --git a/kmymoney/widgets/transaction.h b/kmymoney/widgets/transaction.h index 571d68898..f570bad61 100644 --- a/kmymoney/widgets/transaction.h +++ b/kmymoney/widgets/transaction.h @@ -1,215 +1,215 @@ /*************************************************************************** transaction.h - description ------------------- begin : Tue Jun 13 2006 copyright : (C) 2000-2006 by Thomas Baumgart email : Thomas Baumgart (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 TRANSACTION_H #define TRANSACTION_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "registeritem.h" class QWidget; class QPalette; class QFontMetrics; class QTableWidget; class TransactionEditor; class TransactionEditorContainer; class MyMoneySplit; class MyMoneyTransaction; template class QMap; namespace KMyMoneyTransactionForm { class TransactionForm; } namespace eWidgets { namespace eRegister { enum class Action; } } namespace KMyMoneyRegister { class SelectedTransactions; // keep the following list in sync with code in the constructor // of KMyMoneyRegister::Register in register.cpp class TransactionPrivate; class Transaction : public RegisterItem { Q_DISABLE_COPY(Transaction) public: explicit Transaction(Register* getParent, const MyMoneyTransaction& transaction, const MyMoneySplit& split, int uniqueId); virtual ~Transaction(); virtual const char* className() override; bool isSelectable() const override; bool isSelected() const override; void setSelected(bool selected) override; bool canHaveFocus() const override; bool hasFocus() const override; bool hasEditorOpen() const override; virtual bool isScheduled() const; void setFocus(bool focus, bool updateLens = true) override; bool isErroneous() const override; QDate sortPostDate() const override; virtual int sortSamePostDate() const override; QDate sortEntryDate() const override; virtual const QString& sortPayee() const override; virtual const QList& sortTagList() const; MyMoneyMoney sortValue() const override; QString sortNumber() const override; virtual const QString& sortEntryOrder() const override; virtual eWidgets::eRegister::CashFlowDirection sortType() const override; virtual const QString& sortCategory() const override; virtual eMyMoney::Split::State sortReconcileState() const override; - virtual const QString& id() const override; + virtual QString id() const override; const MyMoneyTransaction& transaction() const; const MyMoneySplit& split() const; void setBalance(const MyMoneyMoney& balance); const MyMoneyMoney& balance() const; virtual int rowHeightHint() const override ; virtual bool paintRegisterCellSetup(QPainter *painter, QStyleOptionViewItem &option, const QModelIndex &index); virtual void paintRegisterCell(QPainter* painter, QStyleOptionViewItem& option, const QModelIndex& index) override; virtual void paintFormCell(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) override; virtual bool formCellText(QString& /* txt */, Qt::Alignment& /* align */, int /* row */, int /* col */, QPainter* /* painter */); virtual void registerCellText(QString& /* txt */, Qt::Alignment& /* align */, int /* row */, int /* col */, QPainter* /* painter */); virtual int registerColWidth(int /* col */, const QFontMetrics& /* cellFontMetrics */); /** * Helper method for the above method. */ void registerCellText(QString& txt, int row, int col); virtual int formRowHeight(int row); virtual int formRowHeight() const; virtual void setupForm(KMyMoneyTransactionForm::TransactionForm* form); virtual void setupFormPalette(QMap& editWidgets); virtual void setupRegisterPalette(QMap& editWidgets); virtual void loadTab(KMyMoneyTransactionForm::TransactionForm* form) = 0; virtual void arrangeWidgetsInForm(QMap& editWidgets) = 0; virtual void arrangeWidgetsInRegister(QMap& editWidgets) = 0; virtual void tabOrderInForm(QWidgetList& tabOrderWidgets) const = 0; virtual void tabOrderInRegister(QWidgetList& tabOrderWidgets) const = 0; virtual eWidgets::eRegister::Action actionType() const = 0; QWidget* focusWidget(QWidget*) const; void arrangeWidget(QTableWidget* tbl, int row, int col, QWidget* w) const; bool haveNumberField() const; bool matches(const RegisterFilter&) const override; /** * Checks if the mouse hovered over an area that has a tooltip associated with it. * The mouse position is given in relative coordinates to the @a startRow and the * @a row and @a col of the item are also passed as relative values. * * If a tooltip shall be shown, this method presets the rectangle @a r with the * area in register coordinates and @a msg with the string that will be passed * to QToolTip::tip. @a true is returned in this case. * * If no tooltip is available, @a false will be returned. */ virtual bool maybeTip(const QPoint& relpos, int row, int col, QRect& r, QString& msg) override; /** * This method returns the number of register rows required for a certain * item in expanded (@p expanded equals @a true) or collapsed (@p expanded * is @a false) mode. * * @param expanded returns number of maximum rows required for this item to * display all information (used for ledger lens and register * edit mode) or the minimum number of rows required. * @return number of rows required for mode selected by @p expanded */ virtual int numRowsRegister(bool expanded) const = 0; /** * Provided for internal reasons. No API change. See RegisterItem::numRowsRegister() */ int numRowsRegister() const override; void leaveEditMode(); void startEditMode(); /** * This method creates an editor for the transaction */ virtual TransactionEditor* createEditor(TransactionEditorContainer* regForm, const KMyMoneyRegister::SelectedTransactions& list, const QDate& lastPostDate) = 0; virtual void setVisible(bool visible) override; virtual void setShowBalance(bool showBalance); /** * Return information if @a row should be shown (@a true ) * or hidden (@a false ) in the form. Default is true. */ virtual bool showRowInForm(int row) const; /** * Control visibility of @a row in the transaction form. * Only row 0 has an effect, others return @a true. */ virtual void setShowRowInForm(int row, bool show); virtual void setReducedIntensity(bool reduced); protected: /** * This method converts m_split.reconcileFlag() into a readable string * * @param text Return textual representation e.g. "Cleared" (@a true) or just * a flag e.g. "C" (@a false). Defaults to textual representation. * @return Textual representation or flag as selected via @p text of the * reconciliation state of the split */ QString reconcileState(bool text = true) const; /** * Helper method to reduce a multi line memo text into a single line. * * @param txt QString that will receive the single line memo text * @param split const reference to the split to take the memo from */ void singleLineMemo(QString& txt, const MyMoneySplit& split) const; virtual void setupPalette(const QPalette& palette, QMap& editWidgets); TransactionPrivate *d_ptr; Transaction(TransactionPrivate &dd, Register* parent, const MyMoneyTransaction& transaction, const MyMoneySplit& split, int uniqueId); Transaction(TransactionPrivate &dd); //for copy-constructor of derived class private: Q_DECLARE_PRIVATE(Transaction) }; } // namespace #endif