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 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #ifndef KACCOUNTSVIEW_P_H
0008 #define KACCOUNTSVIEW_P_H
0009 
0010 #include "kaccountsview.h"
0011 
0012 // ----------------------------------------------------------------------------
0013 // QT Includes
0014 
0015 #include <QAction>
0016 #include <QPointer>
0017 
0018 // ----------------------------------------------------------------------------
0019 // KDE Includes
0020 
0021 #include <KMessageBox>
0022 
0023 // ----------------------------------------------------------------------------
0024 // Project Includes
0025 
0026 #include "ui_kaccountsview.h"
0027 #include "kmymoneyaccountsviewbase_p.h"
0028 
0029 #include "mymoneyexception.h"
0030 #include "mymoneysplit.h"
0031 #include "mymoneyschedule.h"
0032 #include "mymoneytransaction.h"
0033 #include "knewaccountdlg.h"
0034 #include "keditloanwizard.h"
0035 #include "mymoneyaccount.h"
0036 #include "mymoneymoney.h"
0037 #include "mymoneyfile.h"
0038 #include "accountsviewproxymodel.h"
0039 #include "kmymoneyplugin.h"
0040 #include "icons.h"
0041 #include "mymoneyenums.h"
0042 #include "menuenums.h"
0043 #include "mymoneystatementreader.h"
0044 #include "kmymoneyutils.h"
0045 
0046 using namespace Icons;
0047 
0048 class KAccountsViewPrivate : public KMyMoneyAccountsViewBasePrivate
0049 {
0050     Q_DECLARE_PUBLIC(KAccountsView)
0051 
0052 public:
0053     explicit KAccountsViewPrivate(KAccountsView *qq) :
0054         q_ptr(qq),
0055         ui(new Ui::KAccountsView),
0056         m_haveUnusedCategories(false),
0057         m_onlinePlugins(nullptr)
0058     {
0059     }
0060 
0061     ~KAccountsViewPrivate()
0062     {
0063         delete ui;
0064     }
0065 
0066     void init()
0067     {
0068         Q_Q(KAccountsView);
0069         m_accountTree = &ui->m_accountTree;
0070 
0071         // setup icons for collapse and expand button
0072         ui->m_collapseButton->setIcon(Icons::get(Icon::ListCollapse));
0073         ui->m_expandButton->setIcon(Icons::get(Icon::ListExpand));
0074 
0075         m_proxyModel = ui->m_accountTree->init(View::Accounts);
0076         q->connect(m_proxyModel, &AccountsProxyModel::unusedIncomeExpenseAccountHidden, q, &KAccountsView::slotUnusedIncomeExpenseAccountHidden);
0077         q->connect(ui->m_searchWidget, &QLineEdit::textChanged, m_proxyModel, &QSortFilterProxyModel::setFilterFixedString);
0078         q->connect(ui->m_accountTree, &KMyMoneyAccountTreeView::selectByObject, q, &KAccountsView::selectByObject);
0079         q->connect(ui->m_accountTree, &KMyMoneyAccountTreeView::selectByVariant, q, &KAccountsView::selectByVariant);
0080 
0081         q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KAccountsView::refresh);
0082     }
0083 
0084     void editLoan()
0085     {
0086         Q_Q(KAccountsView);
0087         if (m_currentAccount.id().isEmpty())
0088             return;
0089 
0090         const auto file = MyMoneyFile::instance();
0091         if (file->isStandardAccount(m_currentAccount.id()))
0092             return;
0093 
0094         QPointer<KEditLoanWizard> wizard = new KEditLoanWizard(m_currentAccount);
0095         q->connect(wizard, &KEditLoanWizard::newCategory, q, &KAccountsView::slotNewCategory);
0096         q->connect(wizard, &KEditLoanWizard::createPayee, q, &KAccountsView::slotNewPayee);
0097         if (wizard->exec() == QDialog::Accepted && wizard != 0) {
0098             MyMoneySchedule sch;
0099             try {
0100                 sch = file->schedule(m_currentAccount.value("schedule").toLatin1());
0101             } catch (const MyMoneyException &) {
0102                 qDebug() << "schedule" << m_currentAccount.value("schedule").toLatin1() << "not found";
0103             }
0104             if (!(m_currentAccount == wizard->account())
0105                     || !(sch == wizard->schedule())) {
0106                 MyMoneyFileTransaction ft;
0107                 try {
0108                     file->modifyAccount(wizard->account());
0109                     if (!sch.id().isEmpty()) {
0110                         sch = wizard->schedule();
0111                     }
0112                     try {
0113                         file->schedule(sch.id());
0114                         file->modifySchedule(sch);
0115                         ft.commit();
0116                     } catch (const MyMoneyException &) {
0117                         try {
0118                             if(sch.transaction().splitCount() >= 2) {
0119                                 file->addSchedule(sch);
0120                             }
0121                             ft.commit();
0122                         } catch (const MyMoneyException &e) {
0123                             qDebug("Cannot add schedule: '%s'", e.what());
0124                         }
0125                     }
0126                 } catch (const MyMoneyException &e) {
0127                     qDebug("Unable to modify account %s: '%s'", qPrintable(m_currentAccount.name()),
0128                            e.what());
0129                 }
0130             }
0131         }
0132         delete wizard;
0133     }
0134 
0135     void editAccount()
0136     {
0137         if (m_currentAccount.id().isEmpty())
0138             return;
0139 
0140         const auto file = MyMoneyFile::instance();
0141         if (file->isStandardAccount(m_currentAccount.id()))
0142             return;
0143 
0144 
0145         // set a status message so that the application can't be closed until the editing is done
0146         //        slotStatusMsg(caption);
0147 
0148         auto tid = file->openingBalanceTransaction(m_currentAccount);
0149         MyMoneyTransaction t;
0150         MyMoneySplit s0, s1;
0151         QPointer<KNewAccountDlg> dlg =
0152             new KNewAccountDlg(m_currentAccount, true, false, 0, i18n("Edit account '%1'", m_currentAccount.name()));
0153 
0154         if (!tid.isEmpty()) {
0155             try {
0156                 t = file->transaction(tid);
0157                 s0 = t.splitByAccount(m_currentAccount.id());
0158                 s1 = t.splitByAccount(m_currentAccount.id(), false);
0159                 dlg->setOpeningBalance(s0.shares());
0160                 if (m_currentAccount.accountGroup() == eMyMoney::Account::Type::Liability) {
0161                     dlg->setOpeningBalance(-s0.shares());
0162                 }
0163             } catch (const MyMoneyException &e) {
0164                 qDebug() << "Error retrieving opening balance transaction " << tid << ": " << e.what() << "\n";
0165                 tid.clear();
0166             }
0167         }
0168 
0169         // check for online modules
0170         QMap<QString, KMyMoneyPlugin::OnlinePlugin *>::const_iterator it_plugin;
0171         if (m_onlinePlugins) {
0172             it_plugin = m_onlinePlugins->constEnd();
0173             const auto& kvp = m_currentAccount.onlineBankingSettings();
0174             if (!kvp["provider"].isEmpty()) {
0175                 // if we have an online provider for this account, we need to check
0176                 // that we have the corresponding plugin. If that exists, we ask it
0177                 // to provide an additional tab for the account editor.
0178                 it_plugin = m_onlinePlugins->constFind(kvp["provider"].toLower());
0179                 if (it_plugin != m_onlinePlugins->constEnd()) {
0180                     QString name;
0181                     auto w = (*it_plugin)->accountConfigTab(m_currentAccount, name);
0182                     dlg->addTab(w, name);
0183                 }
0184             }
0185         }
0186 
0187         if (dlg != 0 && dlg->exec() == QDialog::Accepted) {
0188             try {
0189                 MyMoneyFileTransaction ft;
0190 
0191                 auto account = dlg->account();
0192                 auto parent = dlg->parentAccount();
0193                 if (m_onlinePlugins && it_plugin != m_onlinePlugins->constEnd()) {
0194                     account.setOnlineBankingSettings((*it_plugin)->onlineBankingSettings(account.onlineBankingSettings()));
0195                 }
0196                 auto bal = dlg->openingBalance();
0197                 if (m_currentAccount.accountGroup() == eMyMoney::Account::Type::Liability) {
0198                     bal = -bal;
0199                 }
0200 
0201                 // we need to modify first, as reparent would override all other changes
0202                 file->modifyAccount(account);
0203                 if (account.parentAccountId() != parent.id()) {
0204                     file->reparentAccount(account, parent);
0205                 }
0206                 if (!tid.isEmpty() && dlg->openingBalance().isZero()) {
0207                     file->removeTransaction(t);
0208 
0209                 } else if (!tid.isEmpty() && !dlg->openingBalance().isZero()) {
0210                     // update transaction only if changed
0211                     if ((s0.shares() != bal) || (t.postDate() != account.openingDate())) {
0212                         s0.setShares(bal);
0213                         s0.setValue(bal);
0214                         t.modifySplit(s0);
0215                         s1.setShares(-bal);
0216                         s1.setValue(-bal);
0217                         t.modifySplit(s1);
0218                         t.setPostDate(account.openingDate());
0219                         file->modifyTransaction(t);
0220                     }
0221                 } else if (tid.isEmpty() && !dlg->openingBalance().isZero()) {
0222                     file->createOpeningBalanceTransaction(m_currentAccount, bal);
0223                 }
0224 
0225                 ft.commit();
0226 
0227                 // reload the account object as it might have changed in the meantime
0228                 //            slotSelectAccount(file->account(account.id()));
0229 
0230             } catch (const MyMoneyException &e) {
0231                 Q_Q(KAccountsView);
0232                 KMessageBox::error(q, i18n("Unable to modify account '%1'. Cause: %2", m_currentAccount.name(), e.what()));
0233             }
0234         }
0235 
0236         delete dlg;
0237         //        ready();
0238 
0239     }
0240 
0241     enum CanCloseAccountCodeE {
0242         AccountCanClose = 0,        // can close the account
0243         AccountBalanceNonZero,      // balance is non zero
0244         AccountChildrenOpen,        // account has open children account
0245         AccountScheduleReference,   // account is referenced in a schedule
0246         AccountHasOnlineMapping,    // account has an online mapping
0247     };
0248 
0249     /**
0250      * This method checks, if an account can be closed or not. An account
0251      * can be closed if:
0252      *
0253      * - the balance is zero and
0254      * - all children are already closed (or it is an investment account and all sub-accounts can be closed) and
0255      * - there is no unfinished schedule referencing the account
0256      * - and no online mapping is setup
0257      *
0258      * @param acc reference to MyMoneyAccount object in question
0259      * @retval true account can be closed
0260      * @retval false account cannot be closed
0261      */
0262     CanCloseAccountCodeE canCloseAccount(const MyMoneyAccount& acc)
0263     {
0264         // balance must be zero
0265         if (!acc.balance().isZero())
0266             return AccountBalanceNonZero;
0267         if (acc.hasOnlineMapping())
0268             return AccountHasOnlineMapping;
0269 
0270         // all children must be already closed
0271         const auto accList = acc.accountList();
0272         for (const auto& sAccount : accList) {
0273             const auto subAccount = MyMoneyFile::instance()->account(sAccount);
0274             if (acc.accountType() == eMyMoney::Account::Type::Investment) {
0275                 auto subAccountResult = canCloseAccount(subAccount);
0276                 if (subAccountResult != AccountCanClose)
0277                     return AccountChildrenOpen;
0278 
0279             } else if (!subAccount.isClosed()) {
0280                 return AccountChildrenOpen;
0281             }
0282         }
0283 
0284         // there must be no unfinished schedule referencing the account
0285         QList<MyMoneySchedule> list = MyMoneyFile::instance()->scheduleList();
0286         QList<MyMoneySchedule>::const_iterator it_l;
0287         for (it_l = list.constBegin(); it_l != list.constEnd(); ++it_l) {
0288             if ((*it_l).isFinished())
0289                 continue;
0290             if ((*it_l).hasReferenceTo(acc.id()))
0291                 return AccountScheduleReference;
0292         }
0293         return AccountCanClose;
0294     }
0295 
0296     /**
0297      * This method checks if an account can be closed and enables/disables
0298      * the close account action
0299      * If disabled, it sets a tooltip explaining why it cannot be closed
0300      * @brief enableCloseAccountAction
0301      * @param acc reference to MyMoneyAccount object in question
0302      */
0303     void hintCloseAccountAction(const MyMoneyAccount& acc, QAction* a)
0304     {
0305         switch (canCloseAccount(acc)) {
0306         case AccountCanClose:
0307             a->setToolTip(QString());
0308             break;
0309         case AccountBalanceNonZero:
0310             a->setToolTip(i18n("The balance of the account must be zero before the account can be closed"));
0311             break;
0312         case AccountChildrenOpen:
0313             a->setToolTip(i18n("All subaccounts must be closed before the account can be closed"));
0314             break;
0315         case AccountScheduleReference:
0316             a->setToolTip(i18n("This account is still included in an active schedule"));
0317             break;
0318         case AccountHasOnlineMapping:
0319             a->setToolTip(i18n("This account is still mapped to an online account"));
0320             break;
0321         }
0322     }
0323 
0324     void accountsUpdateOnline(const QList<MyMoneyAccount>& accList)
0325     {
0326         Q_Q(KAccountsView);
0327 
0328         // block the update account actions for now so that we don't get here twice
0329         const QVector<eMenu::Action> disabledActions {eMenu::Action::UpdateAccount, eMenu::Action::UpdateAllAccounts};
0330         for (const auto& a : disabledActions)
0331             pActions[a]->setEnabled(false);
0332 
0333         // clear global message list
0334         MyMoneyStatementReader::clearResultMessages();
0335 
0336         // process all entries that have a mapped account and the 'provider' is available
0337         // we need to make sure that only the very last entry that matches sets the
0338         // 'moreAccounts' parameter in the call to updateAccount() to false
0339         auto processedAccounts = 0;
0340 
0341         for (auto it_provider = m_onlinePlugins->constBegin(); it_provider != m_onlinePlugins->constEnd(); ++it_provider) {
0342             auto nextAccount = accList.cend();
0343             for (auto it_a = accList.cbegin(); it_a != accList.cend(); ++it_a) {
0344                 if ((*it_a).hasOnlineMapping()
0345                         && (it_provider == m_onlinePlugins->constFind((*it_a).onlineBankingSettings().value("provider").toLower()))) {
0346                     if (nextAccount != accList.cend()) {
0347                         (*it_provider)->updateAccount(*nextAccount, true);
0348                     }
0349                     nextAccount = it_a;
0350                     ++processedAccounts;
0351                 }
0352             }
0353             // process a possible pending entry
0354             if (nextAccount != accList.cend()) {
0355                 (*it_provider)->updateAccount(*nextAccount, false);
0356             }
0357         }
0358 
0359         // re-enable the disabled actions
0360         q->updateActions(m_currentAccount);
0361 
0362         KMyMoneyUtils::showStatementImportResult(MyMoneyStatementReader::resultMessages(), processedAccounts);
0363     }
0364 
0365     void closeSubAccounts(const MyMoneyAccount& account)
0366     {
0367         MyMoneyFile* file = MyMoneyFile::instance();
0368         const auto accountList = account.accountList();
0369 
0370         for (const auto& subAccountId : accountList) {
0371             auto subAccount = file->account(subAccountId);
0372             closeSubAccounts(subAccount);
0373             subAccount.setClosed(true);
0374             file->modifyAccount(subAccount);
0375         }
0376     };
0377 
0378     KAccountsView* q_ptr;
0379     Ui::KAccountsView* ui;
0380     bool m_haveUnusedCategories;
0381     MyMoneyAccount m_currentAccount;
0382     QMap<QString, KMyMoneyPlugin::OnlinePlugin*>* m_onlinePlugins;
0383 };
0384 
0385 #endif