File indexing completed on 2024-05-12 05:07:37
0001 /* 0002 SPDX-FileCopyrightText: 2007-2019 Thomas Baumgart <ipwizard@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2017, 2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> 0004 SPDX-FileCopyrightText: 2020 Robert Szczesiak <dev.rszczesiak@gmail.com> 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "kaccountsview_p.h" 0009 0010 #include <typeinfo> 0011 0012 // ---------------------------------------------------------------------------- 0013 // QT Includes 0014 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 #include "accountdelegate.h" 0034 #include "accountsmodel.h" 0035 0036 using namespace Icons; 0037 0038 KAccountsView::KAccountsView(QWidget *parent) : 0039 KMyMoneyViewBase(*new KAccountsViewPrivate(this), parent) 0040 { 0041 Q_D(KAccountsView); 0042 d->init(); 0043 connect(pActions[eMenu::Action::NewAccount], &QAction::triggered, this, &KAccountsView::slotNewAccount); 0044 connect(pActions[eMenu::Action::EditAccount], &QAction::triggered, this, &KAccountsView::slotEditAccount); 0045 connect(pActions[eMenu::Action::DeleteAccount], &QAction::triggered, this, &KAccountsView::slotDeleteAccount); 0046 connect(pActions[eMenu::Action::MapOnlineAccount], &QAction::triggered, this, &KAccountsView::slotAccountMapOnline); 0047 connect(pActions[eMenu::Action::UnmapOnlineAccount], &QAction::triggered, this, &KAccountsView::slotAccountUnmapOnline); 0048 connect(pActions[eMenu::Action::UpdateAccount], &QAction::triggered, this, &KAccountsView::slotAccountUpdateOnline); 0049 connect(pActions[eMenu::Action::UpdateAllAccounts], &QAction::triggered, this, &KAccountsView::slotAccountUpdateOnlineAll); 0050 0051 d->ui->m_accountTree->setItemDelegate(new AccountDelegate(d->ui->m_accountTree)); 0052 connect(MyMoneyFile::instance()->accountsModel(), &AccountsModel::netWorthChanged, this, &KAccountsView::slotNetWorthChanged); 0053 0054 d->m_sharedToolbarActions.insert(eMenu::Action::FileNew, pActions[eMenu::Action::NewAccount]); 0055 0056 d->ui->m_accountTree->setSelectionMode(QAbstractItemView::SingleSelection); 0057 d->ui->m_accountTree->setDragEnabled(true); 0058 d->ui->m_accountTree->setAcceptDrops(true); 0059 d->ui->m_accountTree->setDropIndicatorShown(true); 0060 d->ui->m_accountTree->setDragDropMode(QAbstractItemView::InternalMove); 0061 } 0062 0063 KAccountsView::~KAccountsView() 0064 { 0065 } 0066 0067 void KAccountsView::slotSettingsChanged() 0068 { 0069 Q_D(KAccountsView); 0070 d->m_proxyModel->setHideClosedAccounts(!KMyMoneySettings::showAllAccounts()); 0071 d->m_proxyModel->setHideEquityAccounts(!KMyMoneySettings::expertMode()); 0072 d->m_proxyModel->setHideZeroBalancedEquityAccounts(KMyMoneySettings::hideZeroBalanceEquities()); 0073 d->m_proxyModel->setHideZeroBalancedAccounts(KMyMoneySettings::hideZeroBalanceAccounts()); 0074 d->m_proxyModel->setShowAllEntries(KMyMoneySettings::showAllAccounts()); 0075 d->m_proxyModel->setHideFavoriteAccounts(false); 0076 0077 if (KMyMoneySettings::showCategoriesInAccountsView()) { 0078 d->m_proxyModel->addAccountGroup(QVector<eMyMoney::Account::Type> {eMyMoney::Account::Type::Income, eMyMoney::Account::Type::Expense}); 0079 } else { 0080 d->m_proxyModel->removeAccountType(eMyMoney::Account::Type::Income); 0081 d->m_proxyModel->removeAccountType(eMyMoney::Account::Type::Expense); 0082 } 0083 0084 MyMoneyFile::instance()->accountsModel()->setColorScheme(AccountsModel::Positive, KMyMoneySettings::schemeColor(SchemeColor::Positive)); 0085 MyMoneyFile::instance()->accountsModel()->setColorScheme(AccountsModel::Negative, KMyMoneySettings::schemeColor(SchemeColor::Negative)); 0086 } 0087 0088 void KAccountsView::executeCustomAction(eView::Action action) 0089 { 0090 switch(action) { 0091 case eView::Action::Refresh: 0092 refresh(); 0093 break; 0094 0095 default: 0096 break; 0097 } 0098 } 0099 0100 void KAccountsView::refresh() 0101 { 0102 Q_D(KAccountsView); 0103 if (!isVisible()) { 0104 d->m_needsRefresh = true; 0105 return; 0106 } 0107 d->m_needsRefresh = false; 0108 0109 d->m_proxyModel->setHideClosedAccounts(!KMyMoneySettings::showAllAccounts()); 0110 d->m_proxyModel->setHideEquityAccounts(!KMyMoneySettings::expertMode()); 0111 d->m_proxyModel->setHideFavoriteAccounts(false); 0112 if (KMyMoneySettings::showCategoriesInAccountsView()) { 0113 d->m_proxyModel->addAccountGroup(QVector<eMyMoney::Account::Type> {eMyMoney::Account::Type::Income, eMyMoney::Account::Type::Expense}); 0114 } else { 0115 d->m_proxyModel->removeAccountType(eMyMoney::Account::Type::Income); 0116 d->m_proxyModel->removeAccountType(eMyMoney::Account::Type::Expense); 0117 } 0118 0119 // reinitialize the default state of the hidden categories label 0120 d->m_haveUnusedCategories = false; 0121 d->ui->m_hiddenCategories->hide(); // hides label 0122 d->m_proxyModel->setHideUnusedIncomeExpenseAccounts(KMyMoneySettings::hideUnusedCategory()); 0123 d->m_proxyModel->invalidate(); 0124 } 0125 0126 void KAccountsView::updateActions(const SelectedObjects& selections) 0127 { 0128 Q_D(KAccountsView); 0129 0130 const auto file = MyMoneyFile::instance(); 0131 0132 // check if there is anything todo and quit if not 0133 if (selections.selection(SelectedObjects::Account).count() < 1 0134 && d->m_currentAccount.id().isEmpty() ) { 0135 return; 0136 } 0137 0138 const QVector<eMenu::Action> actionsToBeDisabled { 0139 eMenu::Action::EditAccount, eMenu::Action::DeleteAccount, 0140 eMenu::Action::CloseAccount, eMenu::Action::ReopenAccount, 0141 eMenu::Action::ChartAccountBalance, 0142 eMenu::Action::UnmapOnlineAccount, eMenu::Action::MapOnlineAccount, 0143 eMenu::Action::UpdateAccount, 0144 }; 0145 0146 for (const auto& a : actionsToBeDisabled) 0147 pActions[a]->setEnabled(false); 0148 0149 pActions[eMenu::Action::NewAccount]->setEnabled(true); 0150 pActions[eMenu::Action::UpdateAllAccounts]->setEnabled(KMyMoneyUtils::canUpdateAllAccounts()); 0151 0152 const auto accountId = selections.firstSelection(SelectedObjects::Account); 0153 if (accountId.isEmpty()) { 0154 d->m_currentAccount = MyMoneyAccount(); 0155 return; 0156 } 0157 const auto acc = file->accountsModel()->itemById(accountId); 0158 d->m_currentAccount = acc; 0159 0160 if (file->isStandardAccount(acc.id())) { 0161 return; 0162 } 0163 0164 d->updateActions(acc); 0165 } 0166 0167 /** 0168 * The view is notified that an unused income expense account has been hidden. 0169 */ 0170 void KAccountsView::slotUnusedIncomeExpenseAccountHidden() 0171 { 0172 Q_D(KAccountsView); 0173 d->m_haveUnusedCategories = true; 0174 d->ui->m_hiddenCategories->setVisible(d->m_haveUnusedCategories); 0175 } 0176 0177 void KAccountsView::slotNetWorthChanged(const MyMoneyMoney &netWorth, bool isApproximate) 0178 { 0179 Q_D(KAccountsView); 0180 const auto formattedValue = d->formatViewLabelValue(netWorth); 0181 d->updateViewLabel(d->ui->m_totalProfitsLabel, 0182 isApproximate ? i18nc("Approximate net worth", "Net Worth: ~%1", formattedValue) 0183 : i18n("Net Worth: %1", formattedValue)); 0184 } 0185 0186 void KAccountsView::setOnlinePlugins(QMap<QString, KMyMoneyPlugin::OnlinePlugin*>* plugins) 0187 { 0188 Q_D(KAccountsView); 0189 d->m_onlinePlugins = plugins; 0190 } 0191 0192 void KAccountsView::slotNewAccount() 0193 { 0194 Q_D(KAccountsView); 0195 MyMoneyAccount account; 0196 account.setOpeningDate(KMyMoneySettings::firstFiscalDate()); 0197 0198 account.setAccountType(d->m_currentAccount.accountType()); 0199 account.setInstitutionId(d->m_currentAccount.institutionId()); 0200 account.setParentAccountId(d->m_currentAccount.id()); 0201 0202 NewAccountWizard::Wizard::newAccount(account); 0203 } 0204 0205 void KAccountsView::slotEditAccount() 0206 { 0207 Q_D(KAccountsView); 0208 0209 switch (d->m_currentAccount.accountType()) { 0210 case eMyMoney::Account::Type::Loan: 0211 case eMyMoney::Account::Type::AssetLoan: 0212 d->editLoan(); 0213 break; 0214 default: 0215 d->editAccount(); 0216 break; 0217 } 0218 } 0219 0220 void KAccountsView::slotDeleteAccount() 0221 { 0222 Q_D(KAccountsView); 0223 if (d->m_currentAccount.id().isEmpty()) 0224 return; // need an account ID 0225 0226 const auto file = MyMoneyFile::instance(); 0227 // can't delete standard accounts or account which still have transactions assigned 0228 if (file->isStandardAccount(d->m_currentAccount.id())) 0229 return; 0230 0231 // check if the account is referenced by a transaction or schedule 0232 QBitArray skip((int)eStorage::Reference::Count); 0233 skip.fill(false); 0234 skip.setBit((int)eStorage::Reference::Account); 0235 skip.setBit((int)eStorage::Reference::Institution); 0236 skip.setBit((int)eStorage::Reference::Payee); 0237 skip.setBit((int)eStorage::Reference::Tag); 0238 skip.setBit((int)eStorage::Reference::Security); 0239 skip.setBit((int)eStorage::Reference::Currency); 0240 skip.setBit((int)eStorage::Reference::Price); 0241 if (file->isReferenced(d->m_currentAccount, skip)) 0242 return; 0243 0244 MyMoneyFileTransaction ft; 0245 0246 // retain the account name for a possible later usage in the error message box 0247 // since the account removal notifies the views the selected account can be changed 0248 // so we make sure by doing this that we display the correct name in the error message 0249 auto selectedAccountName = d->m_currentAccount.name(); 0250 0251 try { 0252 file->removeAccount(d->m_currentAccount); 0253 d->m_currentAccount.clearId(); 0254 ft.commit(); 0255 } catch (const MyMoneyException &e) { 0256 KMessageBox::error(this, i18n("Unable to delete account '%1'. Cause: %2", selectedAccountName, QString::fromLatin1(e.what()))); 0257 } 0258 } 0259 0260 void KAccountsView::slotNewCategory() 0261 { 0262 Q_D(KAccountsView); 0263 KNewAccountDlg::newCategory(d->m_currentAccount, MyMoneyAccount()); 0264 } 0265 0266 void KAccountsView::slotNewPayee(const QString& nameBase, QString& id) 0267 { 0268 bool ok; 0269 std::tie(ok, id) = KMyMoneyUtils::newPayee(nameBase); 0270 } 0271 0272 void KAccountsView::slotAccountUnmapOnline() 0273 { 0274 Q_D(KAccountsView); 0275 // no account selected 0276 if (d->m_currentAccount.id().isEmpty()) 0277 return; 0278 0279 // not a mapped account 0280 if (!d->m_currentAccount.hasOnlineMapping()) 0281 return; 0282 0283 if (KMessageBox::warningTwoActions(this, 0284 QString("<qt>%1</qt>") 0285 .arg(i18n("Do you really want to remove the mapping of account <b>%1</b> to an online account? Depending on the " 0286 "details of the online banking method used, this action cannot be reverted.", 0287 d->m_currentAccount.name())), 0288 i18n("Remove mapping to online account"), 0289 KMMYesNo::yes(), 0290 KMMYesNo::no()) 0291 == KMessageBox::PrimaryAction) { 0292 MyMoneyFileTransaction ft; 0293 try { 0294 d->m_currentAccount.setOnlineBankingSettings(MyMoneyKeyValueContainer()); 0295 // Avoid showing an oline balance 0296 d->m_currentAccount.deletePair(QStringLiteral("lastStatementBalance")); 0297 // delete the kvp that is used in MyMoneyStatementReader too 0298 // we should really get rid of it, but since I don't know what it 0299 // is good for, I'll keep it around. (ipwizard) 0300 d->m_currentAccount.deletePair(QStringLiteral("StatementKey")); 0301 MyMoneyFile::instance()->modifyAccount(d->m_currentAccount); 0302 ft.commit(); 0303 // The mapping could disable the online task system 0304 onlineJobAdministration::instance()->updateOnlineTaskProperties(); 0305 } catch (const MyMoneyException &e) { 0306 KMessageBox::error(this, i18n("Unable to unmap account from online account: %1", QString::fromLatin1(e.what()))); 0307 } 0308 } 0309 d->updateActions(d->m_currentAccount); 0310 } 0311 0312 void KAccountsView::slotAccountMapOnline() 0313 { 0314 Q_D(KAccountsView); 0315 // no account selected 0316 if (d->m_currentAccount.id().isEmpty()) 0317 return; 0318 0319 // already an account mapped 0320 if (d->m_currentAccount.hasOnlineMapping()) 0321 return; 0322 0323 // check if user tries to map a brokerageAccount 0324 if (d->m_currentAccount.name().contains(i18n(" (Brokerage)"))) { 0325 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) { 0326 return; 0327 } 0328 } 0329 if (!d->m_onlinePlugins) 0330 return; 0331 0332 // if we have more than one provider let the user select the current provider 0333 QString provider; 0334 QMap<QString, KMyMoneyPlugin::OnlinePlugin*>::const_iterator it_p; 0335 switch (d->m_onlinePlugins->count()) { 0336 case 0: 0337 break; 0338 case 1: 0339 provider = d->m_onlinePlugins->begin().key(); 0340 break; 0341 default: { 0342 QMenu popup(this); 0343 popup.addSection(i18nc("@title:menu Online provider selection", "Online provider")); 0344 0345 // Populate the pick list with all the provider 0346 for (it_p = d->m_onlinePlugins->constBegin(); it_p != d->m_onlinePlugins->constEnd(); ++it_p) { 0347 popup.addAction(it_p.key())->setData(it_p.key()); 0348 } 0349 0350 QAction* item = popup.actions().at(0); 0351 if (item) { 0352 popup.setActiveAction(item); 0353 } 0354 0355 // cancelled 0356 if ((item = popup.exec(QCursor::pos(), item)) == 0) { 0357 return; 0358 } 0359 0360 provider = item->data().toString(); 0361 } 0362 break; 0363 } 0364 0365 if (provider.isEmpty()) 0366 return; 0367 0368 // find the provider 0369 it_p = d->m_onlinePlugins->constFind(provider.toLower()); 0370 if (it_p != d->m_onlinePlugins->constEnd()) { 0371 // plugin found, call it 0372 MyMoneyKeyValueContainer settings; 0373 if ((*it_p)->mapAccount(d->m_currentAccount, settings)) { 0374 settings["provider"] = provider.toLower(); 0375 MyMoneyAccount acc(d->m_currentAccount); 0376 acc.setOnlineBankingSettings(settings); 0377 MyMoneyFileTransaction ft; 0378 try { 0379 MyMoneyFile::instance()->modifyAccount(acc); 0380 ft.commit(); 0381 // The mapping could enable the online task system 0382 d->m_currentAccount = acc; 0383 onlineJobAdministration::instance()->updateOnlineTaskProperties(); 0384 } catch (const MyMoneyException &e) { 0385 KMessageBox::error(this, i18n("Unable to map account to online account: %1", QString::fromLatin1(e.what()))); 0386 } 0387 } 0388 } 0389 d->updateActions(d->m_currentAccount); 0390 } 0391 0392 void KAccountsView::slotAccountUpdateOnlineAll() 0393 { 0394 Q_D(KAccountsView); 0395 0396 QList<MyMoneyAccount> accList; 0397 MyMoneyFile::instance()->accountList(accList); 0398 0399 QList<MyMoneyAccount> mappedAccList; 0400 for (const auto& account : qAsConst(accList)) { 0401 if (account.hasOnlineMapping()) 0402 mappedAccList += account; 0403 } 0404 0405 d->accountsUpdateOnline(mappedAccList); 0406 } 0407 0408 void KAccountsView::slotAccountUpdateOnline() 0409 { 0410 Q_D(KAccountsView); 0411 // no account selected 0412 if (d->m_currentAccount.id().isEmpty()) 0413 return; 0414 0415 // no online account mapped 0416 if (!d->m_currentAccount.hasOnlineMapping()) 0417 return; 0418 0419 d->accountsUpdateOnline(QList<MyMoneyAccount> { d->m_currentAccount } ); 0420 }