File indexing completed on 2024-05-12 16:43:42
0001 /* 0002 SPDX-FileCopyrightText: 2007 Thomas Baumgart <ipwizard@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2017, 2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "kaccountsview_p.h" 0008 0009 #include <typeinfo> 0010 0011 // ---------------------------------------------------------------------------- 0012 // QT Includes 0013 0014 #include <QTimer> 0015 #include <QMenu> 0016 #include <QAction> 0017 #include <QBitArray> 0018 0019 // ---------------------------------------------------------------------------- 0020 // KDE Includes 0021 0022 #include <KMessageBox> 0023 0024 // ---------------------------------------------------------------------------- 0025 // Project Includes 0026 0027 #include "onlinejobadministration.h" 0028 #include "knewaccountwizard.h" 0029 #include "kmymoneyutils.h" 0030 #include "kmymoneysettings.h" 0031 #include "storageenums.h" 0032 #include "menuenums.h" 0033 0034 using namespace Icons; 0035 0036 KAccountsView::KAccountsView(QWidget *parent) : 0037 KMyMoneyAccountsViewBase(*new KAccountsViewPrivate(this), parent) 0038 { 0039 Q_D(KAccountsView); 0040 d->ui->setupUi(this); 0041 0042 connect(pActions[eMenu::Action::NewAccount], &QAction::triggered, this, &KAccountsView::slotNewAccount); 0043 connect(pActions[eMenu::Action::EditAccount], &QAction::triggered, this, &KAccountsView::slotEditAccount); 0044 connect(pActions[eMenu::Action::DeleteAccount], &QAction::triggered, this, &KAccountsView::slotDeleteAccount); 0045 connect(pActions[eMenu::Action::CloseAccount], &QAction::triggered, this, &KAccountsView::slotCloseAccount); 0046 connect(pActions[eMenu::Action::ReopenAccount], &QAction::triggered, this, &KAccountsView::slotReopenAccount); 0047 connect(pActions[eMenu::Action::ChartAccountBalance], &QAction::triggered, this, &KAccountsView::slotChartAccountBalance); 0048 connect(pActions[eMenu::Action::MapOnlineAccount], &QAction::triggered, this, &KAccountsView::slotAccountMapOnline); 0049 connect(pActions[eMenu::Action::UnmapOnlineAccount], &QAction::triggered, this, &KAccountsView::slotAccountUnmapOnline); 0050 connect(pActions[eMenu::Action::UpdateAccount], &QAction::triggered, this, &KAccountsView::slotAccountUpdateOnline); 0051 connect(pActions[eMenu::Action::UpdateAllAccounts], &QAction::triggered, this, &KAccountsView::slotAccountUpdateOnlineAll); 0052 } 0053 0054 KAccountsView::~KAccountsView() 0055 { 0056 } 0057 0058 void KAccountsView::executeCustomAction(eView::Action action) 0059 { 0060 Q_D(KAccountsView); 0061 switch(action) { 0062 case eView::Action::Refresh: 0063 refresh(); 0064 break; 0065 0066 case eView::Action::SetDefaultFocus: 0067 QTimer::singleShot(0, d->ui->m_accountTree, SLOT(setFocus())); 0068 break; 0069 0070 default: 0071 break; 0072 } 0073 } 0074 0075 void KAccountsView::refresh() 0076 { 0077 Q_D(KAccountsView); 0078 if (!isVisible()) { 0079 d->m_needsRefresh = true; 0080 return; 0081 } 0082 d->m_needsRefresh = false; 0083 // TODO: check why the invalidate is needed here 0084 d->m_proxyModel->invalidate(); 0085 d->m_proxyModel->setHideClosedAccounts(!KMyMoneySettings::showAllAccounts()); 0086 d->m_proxyModel->setHideEquityAccounts(!KMyMoneySettings::expertMode()); 0087 if (KMyMoneySettings::showCategoriesInAccountsView()) { 0088 d->m_proxyModel->addAccountGroup(QVector<eMyMoney::Account::Type> {eMyMoney::Account::Type::Income, eMyMoney::Account::Type::Expense}); 0089 } else { 0090 d->m_proxyModel->removeAccountType(eMyMoney::Account::Type::Income); 0091 d->m_proxyModel->removeAccountType(eMyMoney::Account::Type::Expense); 0092 } 0093 0094 // reinitialize the default state of the hidden categories label 0095 d->m_haveUnusedCategories = false; 0096 d->ui->m_hiddenCategories->hide(); // hides label 0097 d->m_proxyModel->setHideUnusedIncomeExpenseAccounts(KMyMoneySettings::hideUnusedCategory()); 0098 } 0099 0100 void KAccountsView::showEvent(QShowEvent * event) 0101 { 0102 Q_D(KAccountsView); 0103 if (!d->m_proxyModel) 0104 d->init(); 0105 0106 emit customActionRequested(View::Accounts, eView::Action::AboutToShow); 0107 0108 if (d->m_needsRefresh) 0109 refresh(); 0110 0111 // don't forget base class implementation 0112 QWidget::showEvent(event); 0113 } 0114 0115 void KAccountsView::updateActions(const MyMoneyObject& obj) 0116 { 0117 Q_D(KAccountsView); 0118 0119 const auto file = MyMoneyFile::instance(); 0120 0121 if (typeid(obj) != typeid(MyMoneyAccount) && 0122 (obj.id().isEmpty() && d->m_currentAccount.id().isEmpty())) // do not disable actions that were already disabled) 0123 return; 0124 0125 const auto& acc = static_cast<const MyMoneyAccount&>(obj); 0126 0127 const QVector<eMenu::Action> actionsToBeDisabled { 0128 eMenu::Action::NewAccount, eMenu::Action::EditAccount, eMenu::Action::DeleteAccount, 0129 eMenu::Action::CloseAccount, eMenu::Action::ReopenAccount, 0130 eMenu::Action::ChartAccountBalance, 0131 eMenu::Action::UnmapOnlineAccount, eMenu::Action::MapOnlineAccount, 0132 eMenu::Action::UpdateAccount, 0133 }; 0134 0135 for (const auto& a : actionsToBeDisabled) 0136 pActions[a]->setEnabled(false); 0137 0138 pActions[eMenu::Action::NewAccount]->setEnabled(true); 0139 pActions[eMenu::Action::UpdateAllAccounts]->setEnabled(KMyMoneyUtils::canUpdateAllAccounts()); 0140 0141 if (acc.id().isEmpty()) { 0142 d->m_currentAccount = MyMoneyAccount(); 0143 return; 0144 } else if (file->isStandardAccount(acc.id())) { 0145 d->m_currentAccount = acc; 0146 return; 0147 } 0148 d->m_currentAccount = acc; 0149 0150 switch (acc.accountGroup()) { 0151 case eMyMoney::Account::Type::Asset: 0152 case eMyMoney::Account::Type::Liability: 0153 case eMyMoney::Account::Type::Equity: 0154 { 0155 pActions[eMenu::Action::EditAccount]->setEnabled(true); 0156 pActions[eMenu::Action::DeleteAccount]->setEnabled(!file->isReferenced(acc)); 0157 0158 auto b = acc.isClosed() ? true : false; 0159 pActions[eMenu::Action::ReopenAccount]->setEnabled(b); 0160 pActions[eMenu::Action::CloseAccount]->setEnabled(!b); 0161 0162 if (!acc.isClosed()) { 0163 b = (d->canCloseAccount(acc) == KAccountsViewPrivate::AccountCanClose) ? true : false; 0164 pActions[eMenu::Action::CloseAccount]->setEnabled(b); 0165 d->hintCloseAccountAction(acc, pActions[eMenu::Action::CloseAccount]); 0166 } 0167 0168 pActions[eMenu::Action::ChartAccountBalance]->setEnabled(true); 0169 0170 if (d->m_currentAccount.hasOnlineMapping()) { 0171 pActions[eMenu::Action::UnmapOnlineAccount]->setEnabled(true); 0172 0173 if (d->m_onlinePlugins) { 0174 // check if provider is available 0175 QMap<QString, KMyMoneyPlugin::OnlinePlugin*>::const_iterator it_p; 0176 it_p = d->m_onlinePlugins->constFind(d->m_currentAccount.onlineBankingSettings().value(QLatin1String("provider")).toLower()); 0177 if (it_p != d->m_onlinePlugins->constEnd()) { 0178 QStringList protocols; 0179 (*it_p)->protocols(protocols); 0180 if (protocols.count() > 0) { 0181 pActions[eMenu::Action::UpdateAccount]->setEnabled(true); 0182 } 0183 } 0184 } 0185 0186 } else { 0187 pActions[eMenu::Action::MapOnlineAccount]->setEnabled(!acc.isClosed() && d->m_onlinePlugins && !d->m_onlinePlugins->isEmpty()); 0188 } 0189 0190 break; 0191 } 0192 default: 0193 break; 0194 } 0195 0196 #if 0 0197 // It is unclear to me, what the following code should do 0198 // as it just does nothing 0199 QBitArray skip((int)eStorage::Reference::Count); 0200 if (!d->m_currentAccount.id().isEmpty()) { 0201 if (!file->isStandardAccount(d->m_currentAccount.id())) { 0202 switch (d->m_currentAccount.accountGroup()) { 0203 case eMyMoney::Account::Type::Asset: 0204 case eMyMoney::Account::Type::Liability: 0205 case eMyMoney::Account::Type::Equity: 0206 0207 break; 0208 0209 default: 0210 break; 0211 } 0212 } 0213 } 0214 #endif 0215 0216 } 0217 0218 /** 0219 * The view is notified that an unused income expense account has been hidden. 0220 */ 0221 void KAccountsView::slotUnusedIncomeExpenseAccountHidden() 0222 { 0223 Q_D(KAccountsView); 0224 d->m_haveUnusedCategories = true; 0225 d->ui->m_hiddenCategories->setVisible(d->m_haveUnusedCategories); 0226 } 0227 0228 void KAccountsView::slotNetWorthChanged(const MyMoneyMoney &netWorth) 0229 { 0230 Q_D(KAccountsView); 0231 d->netBalProChanged(netWorth, d->ui->m_totalProfitsLabel, View::Accounts); 0232 } 0233 0234 void KAccountsView::slotShowAccountMenu(const MyMoneyAccount& acc) 0235 { 0236 Q_UNUSED(acc); 0237 pMenus[eMenu::Menu::Account]->exec(QCursor::pos()); 0238 } 0239 0240 void KAccountsView::slotSelectByObject(const MyMoneyObject& obj, eView::Intent intent) 0241 { 0242 switch(intent) { 0243 case eView::Intent::UpdateActions: 0244 updateActions(obj); 0245 break; 0246 0247 case eView::Intent::OpenContextMenu: 0248 slotShowAccountMenu(static_cast<const MyMoneyAccount&>(obj)); 0249 break; 0250 0251 default: 0252 break; 0253 } 0254 } 0255 0256 void KAccountsView::slotSelectByVariant(const QVariantList& variant, eView::Intent intent) 0257 { 0258 Q_D(KAccountsView); 0259 switch (intent) { 0260 case eView::Intent::UpdateNetWorth: 0261 if (variant.count() == 1) 0262 slotNetWorthChanged(variant.first().value<MyMoneyMoney>()); 0263 break; 0264 0265 case eView::Intent::SetOnlinePlugins: 0266 if (variant.count() == 1) 0267 d->m_onlinePlugins = static_cast<QMap<QString, KMyMoneyPlugin::OnlinePlugin*>*>(variant.first().value<void*>()); 0268 break; 0269 0270 default: 0271 break; 0272 } 0273 } 0274 0275 void KAccountsView::slotNewAccount() 0276 { 0277 Q_D(KAccountsView); 0278 MyMoneyAccount account; 0279 account.setOpeningDate(KMyMoneySettings::firstFiscalDate()); 0280 account.setParentAccountId(d->m_currentAccount.id()); 0281 account.setAccountType(d->m_currentAccount.accountType()); 0282 NewAccountWizard::Wizard::newAccount(account); 0283 } 0284 0285 void KAccountsView::slotEditAccount() 0286 { 0287 Q_D(KAccountsView); 0288 0289 switch (d->m_currentAccount.accountType()) { 0290 case eMyMoney::Account::Type::Loan: 0291 case eMyMoney::Account::Type::AssetLoan: 0292 d->editLoan(); 0293 break; 0294 default: 0295 d->editAccount(); 0296 break; 0297 } 0298 emit selectByObject(d->m_currentAccount, eView::Intent::None); 0299 } 0300 0301 void KAccountsView::slotDeleteAccount() 0302 { 0303 Q_D(KAccountsView); 0304 if (d->m_currentAccount.id().isEmpty()) 0305 return; // need an account ID 0306 0307 const auto file = MyMoneyFile::instance(); 0308 // can't delete standard accounts or account which still have transactions assigned 0309 if (file->isStandardAccount(d->m_currentAccount.id())) 0310 return; 0311 0312 // check if the account is referenced by a transaction or schedule 0313 QBitArray skip((int)eStorage::Reference::Count); 0314 skip.fill(false); 0315 skip.setBit((int)eStorage::Reference::Account); 0316 skip.setBit((int)eStorage::Reference::Institution); 0317 skip.setBit((int)eStorage::Reference::Payee); 0318 skip.setBit((int)eStorage::Reference::Tag); 0319 skip.setBit((int)eStorage::Reference::Security); 0320 skip.setBit((int)eStorage::Reference::Currency); 0321 skip.setBit((int)eStorage::Reference::Price); 0322 if (file->isReferenced(d->m_currentAccount, skip)) 0323 return; 0324 0325 MyMoneyFileTransaction ft; 0326 0327 // retain the account name for a possible later usage in the error message box 0328 // since the account removal notifies the views the selected account can be changed 0329 // so we make sure by doing this that we display the correct name in the error message 0330 auto selectedAccountName = d->m_currentAccount.name(); 0331 0332 try { 0333 file->removeAccount(d->m_currentAccount); 0334 d->m_currentAccount.clearId(); 0335 emit selectByObject(MyMoneyAccount(), eView::Intent::None); 0336 ft.commit(); 0337 } catch (const MyMoneyException &e) { 0338 KMessageBox::error(this, i18n("Unable to delete account '%1'. Cause: %2", selectedAccountName, QString::fromLatin1(e.what()))); 0339 } 0340 } 0341 0342 void KAccountsView::slotCloseAccount() 0343 { 0344 Q_D(KAccountsView); 0345 MyMoneyFileTransaction ft; 0346 MyMoneyFile* file = MyMoneyFile::instance(); 0347 0348 try { 0349 // in case of investment, try to close the sub-accounts first 0350 if (d->m_currentAccount.accountType() == eMyMoney::Account::Type::Investment) { 0351 d->closeSubAccounts(d->m_currentAccount); 0352 } 0353 d->m_currentAccount.setClosed(true); 0354 file->modifyAccount(d->m_currentAccount); 0355 emit selectByObject(d->m_currentAccount, eView::Intent::None); 0356 ft.commit(); 0357 if (!KMyMoneySettings::showAllAccounts()) 0358 KMessageBox::information(this, 0359 i18n("<qt>You have closed this account. It remains in the system because you have transactions which still refer to it, " 0360 "but it is not shown in the views. You can make it visible again by going to the View menu and selecting <b>Show all " 0361 "accounts</b> or by deselecting the <b>Do not show closed accounts</b> setting.</qt>"), 0362 i18n("Information"), 0363 "CloseAccountInfo"); 0364 } catch (const MyMoneyException& e) { 0365 qDebug() << e.what(); 0366 } 0367 } 0368 0369 void KAccountsView::slotReopenAccount() 0370 { 0371 Q_D(KAccountsView); 0372 const auto file = MyMoneyFile::instance(); 0373 MyMoneyFileTransaction ft; 0374 try { 0375 auto& acc = d->m_currentAccount; 0376 while (acc.isClosed()) { 0377 acc.setClosed(false); 0378 file->modifyAccount(acc); 0379 acc = file->account(acc.parentAccountId()); 0380 } 0381 emit selectByObject(d->m_currentAccount, eView::Intent::None); 0382 ft.commit(); 0383 } catch (const MyMoneyException &) { 0384 } 0385 } 0386 0387 void KAccountsView::slotChartAccountBalance() 0388 { 0389 Q_D(KAccountsView); 0390 if (!d->m_currentAccount.id().isEmpty()) { 0391 emit customActionRequested(View::Accounts, eView::Action::ShowBalanceChart); 0392 } 0393 } 0394 0395 void KAccountsView::slotNewCategory() 0396 { 0397 Q_D(KAccountsView); 0398 KNewAccountDlg::newCategory(d->m_currentAccount, MyMoneyAccount()); 0399 } 0400 0401 void KAccountsView::slotNewPayee(const QString& nameBase, QString& id) 0402 { 0403 KMyMoneyUtils::newPayee(nameBase, id); 0404 } 0405 0406 void KAccountsView::slotAccountUnmapOnline() 0407 { 0408 Q_D(KAccountsView); 0409 // no account selected 0410 if (d->m_currentAccount.id().isEmpty()) 0411 return; 0412 0413 // not a mapped account 0414 if (!d->m_currentAccount.hasOnlineMapping()) 0415 return; 0416 0417 if (KMessageBox::warningYesNo(this, QString("<qt>%1</qt>").arg(i18n("Do you really want to remove the mapping of account <b>%1</b> to an online account? Depending on the details of the online banking method used, this action cannot be reverted.", d->m_currentAccount.name())), i18n("Remove mapping to online account")) == KMessageBox::Yes) { 0418 MyMoneyFileTransaction ft; 0419 try { 0420 d->m_currentAccount.setOnlineBankingSettings(MyMoneyKeyValueContainer()); 0421 // Avoid showing an oline balance 0422 d->m_currentAccount.deletePair(QStringLiteral("lastStatementBalance")); 0423 // delete the kvp that is used in MyMoneyStatementReader too 0424 // we should really get rid of it, but since I don't know what it 0425 // is good for, I'll keep it around. (ipwizard) 0426 d->m_currentAccount.deletePair(QStringLiteral("StatementKey")); 0427 MyMoneyFile::instance()->modifyAccount(d->m_currentAccount); 0428 ft.commit(); 0429 // The mapping could disable the online task system 0430 onlineJobAdministration::instance()->updateOnlineTaskProperties(); 0431 } catch (const MyMoneyException &e) { 0432 KMessageBox::error(this, i18n("Unable to unmap account from online account: %1", QString::fromLatin1(e.what()))); 0433 } 0434 } 0435 updateActions(d->m_currentAccount); 0436 } 0437 0438 void KAccountsView::slotAccountMapOnline() 0439 { 0440 Q_D(KAccountsView); 0441 // no account selected 0442 if (d->m_currentAccount.id().isEmpty()) 0443 return; 0444 0445 // already an account mapped 0446 if (d->m_currentAccount.hasOnlineMapping()) 0447 return; 0448 0449 // check if user tries to map a brokerageAccount 0450 if (d->m_currentAccount.name().contains(i18n(" (Brokerage)"))) { 0451 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) { 0452 return; 0453 } 0454 } 0455 if (!d->m_onlinePlugins) 0456 return; 0457 0458 // if we have more than one provider let the user select the current provider 0459 QString provider; 0460 QMap<QString, KMyMoneyPlugin::OnlinePlugin*>::const_iterator it_p; 0461 switch (d->m_onlinePlugins->count()) { 0462 case 0: 0463 break; 0464 case 1: 0465 provider = d->m_onlinePlugins->begin().key(); 0466 break; 0467 default: { 0468 QMenu popup(this); 0469 popup.setTitle(i18n("Select online banking plugin")); 0470 0471 // Populate the pick list with all the provider 0472 for (it_p = d->m_onlinePlugins->constBegin(); it_p != d->m_onlinePlugins->constEnd(); ++it_p) { 0473 popup.addAction(it_p.key())->setData(it_p.key()); 0474 } 0475 0476 QAction *item = popup.actions()[0]; 0477 if (item) { 0478 popup.setActiveAction(item); 0479 } 0480 0481 // cancelled 0482 if ((item = popup.exec(QCursor::pos(), item)) == 0) { 0483 return; 0484 } 0485 0486 provider = item->data().toString(); 0487 } 0488 break; 0489 } 0490 0491 if (provider.isEmpty()) 0492 return; 0493 0494 // find the provider 0495 it_p = d->m_onlinePlugins->constFind(provider.toLower()); 0496 if (it_p != d->m_onlinePlugins->constEnd()) { 0497 // plugin found, call it 0498 MyMoneyKeyValueContainer settings; 0499 if ((*it_p)->mapAccount(d->m_currentAccount, settings)) { 0500 settings["provider"] = provider.toLower(); 0501 MyMoneyAccount acc(d->m_currentAccount); 0502 acc.setOnlineBankingSettings(settings); 0503 MyMoneyFileTransaction ft; 0504 try { 0505 MyMoneyFile::instance()->modifyAccount(acc); 0506 ft.commit(); 0507 // The mapping could enable the online task system 0508 onlineJobAdministration::instance()->updateOnlineTaskProperties(); 0509 } catch (const MyMoneyException &e) { 0510 KMessageBox::error(this, i18n("Unable to map account to online account: %1", QString::fromLatin1(e.what()))); 0511 } 0512 } 0513 } 0514 updateActions(d->m_currentAccount); 0515 } 0516 0517 void KAccountsView::slotAccountUpdateOnlineAll() 0518 { 0519 Q_D(KAccountsView); 0520 0521 QList<MyMoneyAccount> accList; 0522 MyMoneyFile::instance()->accountList(accList); 0523 0524 QList<MyMoneyAccount> mappedAccList; 0525 Q_FOREACH(auto account, accList) { 0526 if (account.hasOnlineMapping()) 0527 mappedAccList += account; 0528 } 0529 0530 d->accountsUpdateOnline(mappedAccList); 0531 } 0532 0533 void KAccountsView::slotAccountUpdateOnline() 0534 { 0535 Q_D(KAccountsView); 0536 // no account selected 0537 if (d->m_currentAccount.id().isEmpty()) 0538 return; 0539 0540 // no online account mapped 0541 if (!d->m_currentAccount.hasOnlineMapping()) 0542 return; 0543 0544 d->accountsUpdateOnline(QList<MyMoneyAccount> { d->m_currentAccount } ); 0545 }