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 }