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 }