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