1 /*************************************************************************** 2 kaccountsview.cpp 3 ------------------- 4 copyright : (C) 2007 by Thomas Baumgart <ipwizard@users.sourceforge.net> 5 (C) 2017 by Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> 6 ***************************************************************************/ 7 8 /*************************************************************************** 9 * * 10 * This program is free software; you can redistribute it and/or modify * 11 * it under the terms of the GNU General Public License as published by * 12 * the Free Software Foundation; either version 2 of the License, or * 13 * (at your option) any later version. * 14 * * 15 ***************************************************************************/ 16 17 #ifndef KACCOUNTSVIEW_P_H 18 #define KACCOUNTSVIEW_P_H 19 20 #include "kaccountsview.h" 21 22 // ---------------------------------------------------------------------------- 23 // QT Includes 24 25 #include <QAction> 26 #include <QPointer> 27 28 // ---------------------------------------------------------------------------- 29 // KDE Includes 30 31 #include <KMessageBox> 32 33 // ---------------------------------------------------------------------------- 34 // Project Includes 35 36 #include "ui_kaccountsview.h" 37 #include "kmymoneyaccountsviewbase_p.h" 38 39 #include "mymoneyexception.h" 40 #include "mymoneysplit.h" 41 #include "mymoneyschedule.h" 42 #include "mymoneytransaction.h" 43 #include "knewaccountdlg.h" 44 #include "keditloanwizard.h" 45 #include "mymoneyaccount.h" 46 #include "mymoneymoney.h" 47 #include "mymoneyfile.h" 48 #include "accountsviewproxymodel.h" 49 #include "kmymoneyplugin.h" 50 #include "icons.h" 51 #include "mymoneyenums.h" 52 #include "menuenums.h" 53 #include "mymoneystatementreader.h" 54 #include "kmymoneyutils.h" 55 56 using namespace Icons; 57 58 class KAccountsViewPrivate : public KMyMoneyAccountsViewBasePrivate 59 { Q_DECLARE_PUBLIC(KAccountsView)60 Q_DECLARE_PUBLIC(KAccountsView) 61 62 public: 63 explicit KAccountsViewPrivate(KAccountsView *qq) : 64 q_ptr(qq), 65 ui(new Ui::KAccountsView), 66 m_haveUnusedCategories(false), 67 m_onlinePlugins(nullptr) 68 { 69 } 70 ~KAccountsViewPrivate()71 ~KAccountsViewPrivate() 72 { 73 delete ui; 74 } 75 init()76 void init() 77 { 78 Q_Q(KAccountsView); 79 m_accountTree = &ui->m_accountTree; 80 81 // setup icons for collapse and expand button 82 ui->m_collapseButton->setIcon(Icons::get(Icon::ListCollapse)); 83 ui->m_expandButton->setIcon(Icons::get(Icon::ListExpand)); 84 85 m_proxyModel = ui->m_accountTree->init(View::Accounts); 86 q->connect(m_proxyModel, &AccountsProxyModel::unusedIncomeExpenseAccountHidden, q, &KAccountsView::slotUnusedIncomeExpenseAccountHidden); 87 q->connect(ui->m_searchWidget, &QLineEdit::textChanged, m_proxyModel, &QSortFilterProxyModel::setFilterFixedString); 88 q->connect(ui->m_accountTree, &KMyMoneyAccountTreeView::selectByObject, q, &KAccountsView::selectByObject); 89 q->connect(ui->m_accountTree, &KMyMoneyAccountTreeView::selectByVariant, q, &KAccountsView::selectByVariant); 90 91 q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KAccountsView::refresh); 92 } 93 editLoan()94 void editLoan() 95 { 96 Q_Q(KAccountsView); 97 if (m_currentAccount.id().isEmpty()) 98 return; 99 100 const auto file = MyMoneyFile::instance(); 101 if (file->isStandardAccount(m_currentAccount.id())) 102 return; 103 104 QPointer<KEditLoanWizard> wizard = new KEditLoanWizard(m_currentAccount); 105 q->connect(wizard, &KEditLoanWizard::newCategory, q, &KAccountsView::slotNewCategory); 106 q->connect(wizard, &KEditLoanWizard::createPayee, q, &KAccountsView::slotNewPayee); 107 if (wizard->exec() == QDialog::Accepted && wizard != 0) { 108 MyMoneySchedule sch; 109 try { 110 sch = file->schedule(m_currentAccount.value("schedule").toLatin1()); 111 } catch (const MyMoneyException &) { 112 qDebug() << "schedule" << m_currentAccount.value("schedule").toLatin1() << "not found"; 113 } 114 if (!(m_currentAccount == wizard->account()) 115 || !(sch == wizard->schedule())) { 116 MyMoneyFileTransaction ft; 117 try { 118 file->modifyAccount(wizard->account()); 119 if (!sch.id().isEmpty()) { 120 sch = wizard->schedule(); 121 } 122 try { 123 file->schedule(sch.id()); 124 file->modifySchedule(sch); 125 ft.commit(); 126 } catch (const MyMoneyException &) { 127 try { 128 if(sch.transaction().splitCount() >= 2) { 129 file->addSchedule(sch); 130 } 131 ft.commit(); 132 } catch (const MyMoneyException &e) { 133 qDebug("Cannot add schedule: '%s'", e.what()); 134 } 135 } 136 } catch (const MyMoneyException &e) { 137 qDebug("Unable to modify account %s: '%s'", qPrintable(m_currentAccount.name()), 138 e.what()); 139 } 140 } 141 } 142 delete wizard; 143 } 144 editAccount()145 void editAccount() 146 { 147 if (m_currentAccount.id().isEmpty()) 148 return; 149 150 const auto file = MyMoneyFile::instance(); 151 if (file->isStandardAccount(m_currentAccount.id())) 152 return; 153 154 155 // set a status message so that the application can't be closed until the editing is done 156 // slotStatusMsg(caption); 157 158 auto tid = file->openingBalanceTransaction(m_currentAccount); 159 MyMoneyTransaction t; 160 MyMoneySplit s0, s1; 161 QPointer<KNewAccountDlg> dlg = 162 new KNewAccountDlg(m_currentAccount, true, false, 0, i18n("Edit account '%1'", m_currentAccount.name())); 163 164 if (!tid.isEmpty()) { 165 try { 166 t = file->transaction(tid); 167 s0 = t.splitByAccount(m_currentAccount.id()); 168 s1 = t.splitByAccount(m_currentAccount.id(), false); 169 dlg->setOpeningBalance(s0.shares()); 170 if (m_currentAccount.accountGroup() == eMyMoney::Account::Type::Liability) { 171 dlg->setOpeningBalance(-s0.shares()); 172 } 173 } catch (const MyMoneyException &e) { 174 qDebug() << "Error retrieving opening balance transaction " << tid << ": " << e.what() << "\n"; 175 tid.clear(); 176 } 177 } 178 179 // check for online modules 180 QMap<QString, KMyMoneyPlugin::OnlinePlugin *>::const_iterator it_plugin; 181 if (m_onlinePlugins) { 182 it_plugin = m_onlinePlugins->constEnd(); 183 const auto& kvp = m_currentAccount.onlineBankingSettings(); 184 if (!kvp["provider"].isEmpty()) { 185 // if we have an online provider for this account, we need to check 186 // that we have the corresponding plugin. If that exists, we ask it 187 // to provide an additional tab for the account editor. 188 it_plugin = m_onlinePlugins->constFind(kvp["provider"].toLower()); 189 if (it_plugin != m_onlinePlugins->constEnd()) { 190 QString name; 191 auto w = (*it_plugin)->accountConfigTab(m_currentAccount, name); 192 dlg->addTab(w, name); 193 } 194 } 195 } 196 197 if (dlg != 0 && dlg->exec() == QDialog::Accepted) { 198 try { 199 MyMoneyFileTransaction ft; 200 201 auto account = dlg->account(); 202 auto parent = dlg->parentAccount(); 203 if (m_onlinePlugins && it_plugin != m_onlinePlugins->constEnd()) { 204 account.setOnlineBankingSettings((*it_plugin)->onlineBankingSettings(account.onlineBankingSettings())); 205 } 206 auto bal = dlg->openingBalance(); 207 if (m_currentAccount.accountGroup() == eMyMoney::Account::Type::Liability) { 208 bal = -bal; 209 } 210 211 // we need to modify first, as reparent would override all other changes 212 file->modifyAccount(account); 213 if (account.parentAccountId() != parent.id()) { 214 file->reparentAccount(account, parent); 215 } 216 if (!tid.isEmpty() && dlg->openingBalance().isZero()) { 217 file->removeTransaction(t); 218 219 } else if (!tid.isEmpty() && !dlg->openingBalance().isZero()) { 220 // update transaction only if changed 221 if ((s0.shares() != bal) || (t.postDate() != account.openingDate())) { 222 s0.setShares(bal); 223 s0.setValue(bal); 224 t.modifySplit(s0); 225 s1.setShares(-bal); 226 s1.setValue(-bal); 227 t.modifySplit(s1); 228 t.setPostDate(account.openingDate()); 229 file->modifyTransaction(t); 230 } 231 } else if (tid.isEmpty() && !dlg->openingBalance().isZero()) { 232 file->createOpeningBalanceTransaction(m_currentAccount, bal); 233 } 234 235 ft.commit(); 236 237 // reload the account object as it might have changed in the meantime 238 // slotSelectAccount(file->account(account.id())); 239 240 } catch (const MyMoneyException &e) { 241 Q_Q(KAccountsView); 242 KMessageBox::error(q, i18n("Unable to modify account '%1'. Cause: %2", m_currentAccount.name(), e.what())); 243 } 244 } 245 246 delete dlg; 247 // ready(); 248 249 } 250 251 enum CanCloseAccountCodeE { 252 AccountCanClose = 0, // can close the account 253 AccountBalanceNonZero, // balance is non zero 254 AccountChildrenOpen, // account has open children account 255 AccountScheduleReference, // account is referenced in a schedule 256 AccountHasOnlineMapping, // account has an online mapping 257 }; 258 259 /** 260 * This method checks, if an account can be closed or not. An account 261 * can be closed if: 262 * 263 * - the balance is zero and 264 * - all children are already closed and 265 * - there is no unfinished schedule referencing the account 266 * - and no online mapping is setup 267 * 268 * @param acc reference to MyMoneyAccount object in question 269 * @retval true account can be closed 270 * @retval false account cannot be closed 271 */ canCloseAccount(const MyMoneyAccount & acc)272 CanCloseAccountCodeE canCloseAccount(const MyMoneyAccount& acc) 273 { 274 // balance must be zero 275 if (!acc.balance().isZero()) 276 return AccountBalanceNonZero; 277 if (acc.hasOnlineMapping()) 278 return AccountHasOnlineMapping; 279 280 // all children must be already closed 281 foreach (const auto sAccount, acc.accountList()) { 282 if (!MyMoneyFile::instance()->account(sAccount).isClosed()) { 283 return AccountChildrenOpen; 284 } 285 } 286 287 // there must be no unfinished schedule referencing the account 288 QList<MyMoneySchedule> list = MyMoneyFile::instance()->scheduleList(); 289 QList<MyMoneySchedule>::const_iterator it_l; 290 for (it_l = list.constBegin(); it_l != list.constEnd(); ++it_l) { 291 if ((*it_l).isFinished()) 292 continue; 293 if ((*it_l).hasReferenceTo(acc.id())) 294 return AccountScheduleReference; 295 } 296 return AccountCanClose; 297 } 298 299 /** 300 * This method checks if an account can be closed and enables/disables 301 * the close account action 302 * If disabled, it sets a tooltip explaining why it cannot be closed 303 * @brief enableCloseAccountAction 304 * @param acc reference to MyMoneyAccount object in question 305 */ hintCloseAccountAction(const MyMoneyAccount & acc,QAction * a)306 void hintCloseAccountAction(const MyMoneyAccount& acc, QAction* a) 307 { 308 switch (canCloseAccount(acc)) { 309 case AccountCanClose: 310 a->setToolTip(QString()); 311 break; 312 case AccountBalanceNonZero: 313 a->setToolTip(i18n("The balance of the account must be zero before the account can be closed")); 314 break; 315 case AccountChildrenOpen: 316 a->setToolTip(i18n("All subaccounts must be closed before the account can be closed")); 317 break; 318 case AccountScheduleReference: 319 a->setToolTip(i18n("This account is still included in an active schedule")); 320 break; 321 case AccountHasOnlineMapping: 322 a->setToolTip(i18n("This account is still mapped to an online account")); 323 break; 324 } 325 } 326 accountsUpdateOnline(const QList<MyMoneyAccount> & accList)327 void accountsUpdateOnline(const QList<MyMoneyAccount>& accList) 328 { 329 Q_Q(KAccountsView); 330 331 // block the update account actions for now so that we don't get here twice 332 const QVector<eMenu::Action> disabledActions {eMenu::Action::UpdateAccount, eMenu::Action::UpdateAllAccounts}; 333 for (const auto& a : disabledActions) 334 pActions[a]->setEnabled(false); 335 336 // clear global message list 337 MyMoneyStatementReader::clearResultMessages(); 338 339 // process all entries that have a mapped account and the 'provider' is available 340 // we need to make sure that only the very last entry that matches sets the 341 // 'moreAccounts' parameter in the call to updateAccount() to false 342 auto processedAccounts = 0; 343 344 for (auto it_provider = m_onlinePlugins->constBegin(); it_provider != m_onlinePlugins->constEnd(); ++it_provider) { 345 auto nextAccount = accList.cend(); 346 for (auto it_a = accList.cbegin(); it_a != accList.cend(); ++it_a) { 347 if ((*it_a).hasOnlineMapping() 348 && (it_provider == m_onlinePlugins->constFind((*it_a).onlineBankingSettings().value("provider").toLower()))) { 349 if (nextAccount != accList.cend()) { 350 (*it_provider)->updateAccount(*nextAccount, true); 351 } 352 nextAccount = it_a; 353 ++processedAccounts; 354 } 355 } 356 // process a possible pending entry 357 if (nextAccount != accList.cend()) { 358 (*it_provider)->updateAccount(*nextAccount, false); 359 } 360 } 361 362 // re-enable the disabled actions 363 q->updateActions(m_currentAccount); 364 365 KMyMoneyUtils::showStatementImportResult(MyMoneyStatementReader::resultMessages(), processedAccounts); 366 } 367 368 KAccountsView *q_ptr; 369 Ui::KAccountsView *ui; 370 bool m_haveUnusedCategories; 371 MyMoneyAccount m_currentAccount; 372 QMap<QString, KMyMoneyPlugin::OnlinePlugin*>* m_onlinePlugins; 373 }; 374 375 #endif 376