File indexing completed on 2024-05-12 16:43:44
0001 /* 0002 SPDX-FileCopyrightText: 2006 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 #include "kgloballedgerview_p.h" 0008 0009 #include <typeinfo> 0010 0011 // ---------------------------------------------------------------------------- 0012 // QT Includes 0013 0014 #include <QFrame> 0015 #include <QList> 0016 #include <QLabel> 0017 #include <QEvent> 0018 #include <QApplication> 0019 #include <QTimer> 0020 #include <QMenu> 0021 #include <QClipboard> 0022 0023 // ---------------------------------------------------------------------------- 0024 // KDE Includes 0025 0026 #include <KLocalizedString> 0027 #include <KMessageBox> 0028 #include <KToolBar> 0029 #include <KActionCollection> 0030 #include <KXmlGuiWindow> 0031 0032 // ---------------------------------------------------------------------------- 0033 // Project Includes 0034 0035 #include "mymoneyaccount.h" 0036 #include "mymoneyfile.h" 0037 #include "kmymoneyaccountcombo.h" 0038 #include "kmymoneypayeecombo.h" 0039 #include "keditscheduledlg.h" 0040 #include "kendingbalancedlg.h" 0041 #include "register.h" 0042 #include "transactioneditor.h" 0043 #include "selectedtransactions.h" 0044 #include "kmymoneysettings.h" 0045 #include "registersearchline.h" 0046 #include "kfindtransactiondlg.h" 0047 #include "accountsmodel.h" 0048 #include "models.h" 0049 #include "mymoneyschedule.h" 0050 #include "mymoneysecurity.h" 0051 #include "mymoneytransaction.h" 0052 #include "mymoneytransactionfilter.h" 0053 #include "mymoneysplit.h" 0054 #include "transaction.h" 0055 #include "transactionform.h" 0056 #include "widgetenums.h" 0057 #include "mymoneyenums.h" 0058 #include "menuenums.h" 0059 0060 using namespace eMenu; 0061 0062 QDate KGlobalLedgerViewPrivate::m_lastPostDate; 0063 0064 KGlobalLedgerView::KGlobalLedgerView(QWidget *parent) : 0065 KMyMoneyViewBase(*new KGlobalLedgerViewPrivate(this), parent) 0066 { 0067 // clang-format off 0068 const QHash<Action, std::function<void()>> actionConnections { 0069 {Action::NewTransaction, [this](){ KGlobalLedgerView::slotNewTransaction(); }}, 0070 {Action::EditTransaction, [this](){ KGlobalLedgerView::slotEditTransaction(); }}, 0071 {Action::DeleteTransaction, [this](){ KGlobalLedgerView::slotDeleteTransaction(); }}, 0072 {Action::DuplicateTransaction, [this](){ KGlobalLedgerView::slotDuplicateTransaction(); }}, 0073 {Action::AddReversingTransaction, [this](){ KGlobalLedgerView::slotDuplicateTransaction(true); }}, 0074 {Action::EnterTransaction, [this](){ KGlobalLedgerView::slotEnterTransaction(); }}, 0075 {Action::AcceptTransaction, [this](){ KGlobalLedgerView::slotAcceptTransaction(); }}, 0076 {Action::CancelTransaction, [this](){ KGlobalLedgerView::slotCancelTransaction(); }}, 0077 {Action::EditSplits, [this](){ KGlobalLedgerView::slotEditSplits(); }}, 0078 {Action::CopySplits, [this](){ KGlobalLedgerView::slotCopySplits(); }}, 0079 {Action::GoToPayee, [this](){ KGlobalLedgerView::slotGoToPayee(); }}, 0080 {Action::GoToAccount, [this](){ KGlobalLedgerView::slotGoToAccount(); }}, 0081 {Action::MatchTransaction, [this](){ KGlobalLedgerView::slotMatchTransactions(); }}, 0082 {Action::CombineTransactions, [this](){ KGlobalLedgerView::slotCombineTransactions(); }}, 0083 {Action::ToggleReconciliationFlag, [this](){ KGlobalLedgerView::slotToggleReconciliationFlag(); }}, 0084 {Action::MarkCleared, [this](){ KGlobalLedgerView::slotMarkCleared(); }}, 0085 {Action::MarkReconciled, [this](){ KGlobalLedgerView::slotMarkReconciled(); }}, 0086 {Action::MarkNotReconciled, [this](){ KGlobalLedgerView::slotMarkNotReconciled(); }}, 0087 {Action::SelectAllTransactions, [this](){ KGlobalLedgerView::slotSelectAllTransactions(); }}, 0088 {Action::NewScheduledTransaction, [this](){ KGlobalLedgerView::slotCreateScheduledTransaction(); }}, 0089 {Action::AssignTransactionsNumber, [this](){ KGlobalLedgerView::slotAssignNumber(); }}, 0090 {Action::StartReconciliation, [this](){ KGlobalLedgerView::slotStartReconciliation(); }}, 0091 {Action::FinishReconciliation, [this](){ KGlobalLedgerView::slotFinishReconciliation(); }}, 0092 {Action::PostponeReconciliation, [this](){ KGlobalLedgerView::slotPostponeReconciliation(); }}, 0093 {Action::OpenAccount, [this](){ KGlobalLedgerView::slotOpenAccount(); }}, 0094 {Action::EditFindTransaction, [this](){ KGlobalLedgerView::slotFindTransaction(); }}, 0095 }; 0096 // clang-format on 0097 0098 for (auto a = actionConnections.cbegin(); a != actionConnections.cend(); ++a) 0099 connect(pActions[a.key()], &QAction::triggered, this, a.value()); 0100 0101 KXmlGuiWindow* mw = KMyMoneyUtils::mainWindow(); 0102 KStandardAction::copy(this, &KGlobalLedgerView::slotCopyTransactionToClipboard, mw->actionCollection()); 0103 0104 Q_D(KGlobalLedgerView); 0105 d->m_balanceWarning.reset(new KBalanceWarning(this)); 0106 } 0107 0108 KGlobalLedgerView::~KGlobalLedgerView() 0109 { 0110 } 0111 0112 void KGlobalLedgerView::executeCustomAction(eView::Action action) 0113 { 0114 Q_D(KGlobalLedgerView); 0115 switch(action) { 0116 case eView::Action::Refresh: 0117 refresh(); 0118 break; 0119 0120 case eView::Action::SetDefaultFocus: 0121 // delay the setFocus call until the event loop is running 0122 QMetaObject::invokeMethod(d->m_registerSearchLine->searchLine(), "setFocus", Qt::QueuedConnection); 0123 break; 0124 0125 case eView::Action::DisableViewDepenedendActions: 0126 pActions[Action::SelectAllTransactions]->setEnabled(false); 0127 break; 0128 0129 case eView::Action::InitializeAfterFileOpen: 0130 d->m_lastSelectedAccountID.clear(); 0131 d->m_currentAccount = MyMoneyAccount(); 0132 if (d->m_accountComboBox) { 0133 d->m_accountComboBox->setSelected(QString()); 0134 } 0135 break; 0136 0137 case eView::Action::CleanupBeforeFileClose: 0138 if (d->m_inEditMode) { 0139 d->deleteTransactionEditor(); 0140 } 0141 break; 0142 0143 default: 0144 break; 0145 } 0146 } 0147 0148 void KGlobalLedgerView::refresh() 0149 { 0150 Q_D(KGlobalLedgerView); 0151 if (isVisible()) { 0152 if (!d->m_inEditMode) { 0153 setUpdatesEnabled(false); 0154 d->loadView(); 0155 setUpdatesEnabled(true); 0156 d->m_needsRefresh = false; 0157 // force a new account if the current one is empty 0158 d->m_newAccountLoaded = d->m_currentAccount.id().isEmpty(); 0159 } 0160 } else { 0161 d->m_needsRefresh = true; 0162 } 0163 } 0164 0165 void KGlobalLedgerView::showEvent(QShowEvent* event) 0166 { 0167 if (MyMoneyFile::instance()->storageAttached()) { 0168 Q_D(KGlobalLedgerView); 0169 if (d->m_needLoad) 0170 d->init(); 0171 0172 emit customActionRequested(View::Ledgers, eView::Action::AboutToShow); 0173 0174 if (d->m_needsRefresh) { 0175 if (!d->m_inEditMode) { 0176 setUpdatesEnabled(false); 0177 d->loadView(); 0178 setUpdatesEnabled(true); 0179 d->m_needsRefresh = false; 0180 d->m_newAccountLoaded = false; 0181 } 0182 0183 } else { 0184 if (!d->m_lastSelectedAccountID.isEmpty()) { 0185 try { 0186 const auto acc = MyMoneyFile::instance()->account(d->m_lastSelectedAccountID); 0187 slotSelectAccount(acc.id()); 0188 } catch (const MyMoneyException &) { 0189 d->m_lastSelectedAccountID.clear(); // account is invalid 0190 } 0191 } else { 0192 slotSelectAccount(d->m_accountComboBox->getSelected()); 0193 } 0194 0195 KMyMoneyRegister::SelectedTransactions list(d->m_register); 0196 updateLedgerActions(list); 0197 emit selectByVariant(QVariantList {QVariant::fromValue(list)}, eView::Intent::SelectRegisterTransactions); 0198 } 0199 } 0200 0201 pActions[Action::SelectAllTransactions]->setEnabled(true); 0202 // don't forget base class implementation 0203 QWidget::showEvent(event); 0204 } 0205 0206 void KGlobalLedgerView::updateActions(const MyMoneyObject& obj) 0207 { 0208 Q_D(KGlobalLedgerView); 0209 // if (typeid(obj) != typeid(MyMoneyAccount) && 0210 // (obj.id().isEmpty() && d->m_currentAccount.id().isEmpty())) // do not disable actions that were already disabled)) 0211 // return; 0212 0213 const auto& acc = static_cast<const MyMoneyAccount&>(obj); 0214 0215 const QVector<Action> actionsToBeDisabled { 0216 Action::StartReconciliation, 0217 Action::FinishReconciliation, 0218 Action::PostponeReconciliation, 0219 Action::OpenAccount, 0220 Action::NewTransaction, 0221 }; 0222 0223 for (const auto& a : actionsToBeDisabled) 0224 pActions[a]->setEnabled(false); 0225 0226 auto b = acc.isClosed() ? false : true; 0227 pMenus[Menu::MoveTransaction]->setEnabled(b); 0228 0229 QString tooltip; 0230 pActions[Action::NewTransaction]->setEnabled(canCreateTransactions(tooltip) || !isVisible()); 0231 pActions[Action::NewTransaction]->setToolTip(tooltip); 0232 0233 const auto file = MyMoneyFile::instance(); 0234 if (!acc.id().isEmpty() && !file->isStandardAccount(acc.id())) { 0235 switch (acc.accountGroup()) { 0236 case eMyMoney::Account::Type::Asset: 0237 case eMyMoney::Account::Type::Liability: 0238 case eMyMoney::Account::Type::Equity: 0239 pActions[Action::OpenAccount]->setEnabled(true); 0240 if (acc.accountGroup() != eMyMoney::Account::Type::Equity) { 0241 if (d->m_reconciliationAccount.id().isEmpty()) { 0242 pActions[Action::StartReconciliation]->setEnabled(true); 0243 pActions[Action::StartReconciliation]->setToolTip(i18n("Reconcile")); 0244 } else { 0245 auto tip = i18n("Reconcile - disabled because you are currently reconciling <b>%1</b>", d->m_reconciliationAccount.name()); 0246 pActions[Action::StartReconciliation]->setToolTip(tip); 0247 if (!d->m_transactionEditor) { 0248 pActions[Action::FinishReconciliation]->setEnabled(acc.id() == d->m_reconciliationAccount.id()); 0249 pActions[Action::PostponeReconciliation]->setEnabled(acc.id() == d->m_reconciliationAccount.id()); 0250 } 0251 } 0252 } 0253 break; 0254 case eMyMoney::Account::Type::Income : 0255 case eMyMoney::Account::Type::Expense : 0256 pActions[Action::OpenAccount]->setEnabled(true); 0257 break; 0258 default: 0259 break; 0260 } 0261 } 0262 0263 d->m_currentAccount = acc; 0264 // slotSelectAccount(acc); 0265 } 0266 0267 void KGlobalLedgerView::updateLedgerActions(const KMyMoneyRegister::SelectedTransactions& list) 0268 { 0269 Q_D(KGlobalLedgerView); 0270 0271 d->selectTransactions(list); 0272 updateLedgerActionsInternal(); 0273 } 0274 0275 void KGlobalLedgerView::updateLedgerActionsInternal() 0276 { 0277 Q_D(KGlobalLedgerView); 0278 const QVector<Action> actionsToBeDisabled { 0279 Action::EditTransaction, Action::EditSplits, Action::EnterTransaction, 0280 Action::CancelTransaction, Action::DeleteTransaction, Action::MatchTransaction, 0281 Action::AcceptTransaction, Action::DuplicateTransaction, Action::AddReversingTransaction, Action::ToggleReconciliationFlag, Action::MarkCleared, 0282 Action::GoToAccount, Action::GoToPayee, Action::AssignTransactionsNumber, Action::NewScheduledTransaction, 0283 Action::CombineTransactions, Action::CopySplits, 0284 }; 0285 0286 for (const auto& a : actionsToBeDisabled) 0287 pActions[a]->setEnabled(false); 0288 0289 const auto file = MyMoneyFile::instance(); 0290 0291 pActions[Action::MatchTransaction]->setText(i18nc("Button text for match transaction", "Match")); 0292 // pActions[Action::TransactionNew]->setToolTip(i18n("Create a new transaction")); 0293 0294 pMenus[Menu::MoveTransaction]->setEnabled(false); 0295 pMenus[Menu::MarkTransaction]->setEnabled(false); 0296 pMenus[Menu::MarkTransactionContext]->setEnabled(false); 0297 0298 if (!d->m_selectedTransactions.isEmpty() && !d->m_selectedTransactions.first().isScheduled()) { 0299 // enable 'delete transaction' only if at least one of the 0300 // selected transactions does not reference a closed account 0301 bool enable = false; 0302 KMyMoneyRegister::SelectedTransactions::const_iterator it_t; 0303 for (it_t = d->m_selectedTransactions.constBegin(); (enable == false) && (it_t != d->m_selectedTransactions.constEnd()); ++it_t) { 0304 enable = !(*it_t).transaction().id().isEmpty() && !file->referencesClosedAccount((*it_t).transaction()); 0305 } 0306 pActions[Action::DeleteTransaction]->setEnabled(enable); 0307 0308 if (!d->m_transactionEditor) { 0309 QString tooltip = i18n("Duplicate the current selected transactions"); 0310 pActions[Action::DuplicateTransaction]->setEnabled(canDuplicateTransactions(d->m_selectedTransactions, tooltip) && !d->m_selectedTransactions[0].transaction().id().isEmpty()); 0311 pActions[Action::DuplicateTransaction]->setToolTip(tooltip); 0312 0313 tooltip = i18n("Add reversing transactions to the currently selected"); 0314 pActions[Action::AddReversingTransaction]->setEnabled(canDuplicateTransactions(d->m_selectedTransactions, tooltip) && !d->m_selectedTransactions[0].transaction().id().isEmpty()); 0315 pActions[Action::AddReversingTransaction]->setToolTip(tooltip); 0316 0317 if (canEditTransactions(d->m_selectedTransactions, tooltip)) { 0318 pActions[Action::EditTransaction]->setEnabled(true); 0319 // editing splits is allowed only if we have one transaction selected 0320 if (d->m_selectedTransactions.count() == 1) { 0321 pActions[Action::EditSplits]->setEnabled(true); 0322 } 0323 if (d->m_currentAccount.isAssetLiability() && (d->m_currentAccount.accountType() != eMyMoney::Account::Type::Investment) 0324 && canCreateTransactions(tooltip)) { 0325 pActions[Action::NewScheduledTransaction]->setEnabled(d->m_selectedTransactions.count() == 1); 0326 } 0327 } 0328 pActions[Action::EditTransaction]->setToolTip(tooltip); 0329 0330 if (!d->m_currentAccount.isClosed()) 0331 pMenus[Menu::MoveTransaction]->setEnabled(true); 0332 0333 pMenus[Menu::MarkTransaction]->setEnabled(true); 0334 pMenus[Menu::MarkTransactionContext]->setEnabled(true); 0335 0336 // Allow marking the transaction if at least one is selected 0337 pActions[Action::MarkCleared]->setEnabled(true); 0338 pActions[Action::MarkReconciled]->setEnabled(true); 0339 pActions[Action::MarkNotReconciled]->setEnabled(true); 0340 pActions[Action::ToggleReconciliationFlag]->setEnabled(true); 0341 0342 if (!d->m_accountGoto.isEmpty()) 0343 pActions[Action::GoToAccount]->setEnabled(true); 0344 if (!d->m_payeeGoto.isEmpty()) 0345 pActions[Action::GoToPayee]->setEnabled(true); 0346 0347 // Matching is enabled as soon as one regular and one imported transaction is selected 0348 int matchedCount = 0; 0349 int importedCount = 0; 0350 KMyMoneyRegister::SelectedTransactions::const_iterator it; 0351 for (it = d->m_selectedTransactions.constBegin(); it != d->m_selectedTransactions.constEnd(); ++it) { 0352 if ((*it).transaction().isImported()) 0353 ++importedCount; 0354 if ((*it).split().isMatched()) 0355 ++matchedCount; 0356 } 0357 0358 if (d->m_selectedTransactions.count() == 2 /* && pActions[Action::TransactionEdit]->isEnabled() */) { 0359 pActions[Action::MatchTransaction]->setEnabled(true); 0360 } 0361 if (importedCount != 0 || matchedCount != 0) 0362 pActions[Action::AcceptTransaction]->setEnabled(true); 0363 if (matchedCount != 0) { 0364 pActions[Action::MatchTransaction]->setEnabled(true); 0365 pActions[Action::MatchTransaction]->setText(i18nc("Button text for unmatch transaction", "Unmatch")); 0366 pActions[Action::MatchTransaction]->setIcon(QIcon("process-stop")); 0367 } 0368 0369 if (d->m_selectedTransactions.count() > 1) { 0370 pActions[Action::CombineTransactions]->setEnabled(true); 0371 } 0372 if (d->m_selectedTransactions.count() >= 2) { 0373 int singleSplitTransactions = 0; 0374 int multipleSplitTransactions = 0; 0375 foreach (const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) { 0376 switch (st.transaction().splitCount()) { 0377 case 0: 0378 break; 0379 case 1: 0380 singleSplitTransactions++; 0381 break; 0382 default: 0383 multipleSplitTransactions++; 0384 break; 0385 } 0386 } 0387 if (singleSplitTransactions > 0 && multipleSplitTransactions == 1) { 0388 pActions[Action::CopySplits]->setEnabled(true); 0389 } 0390 } 0391 if (d->m_selectedTransactions.count() >= 2) { 0392 int singleSplitTransactions = 0; 0393 int multipleSplitTransactions = 0; 0394 foreach(const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) { 0395 switch(st.transaction().splitCount()) { 0396 case 0: 0397 break; 0398 case 1: 0399 singleSplitTransactions++; 0400 break; 0401 default: 0402 multipleSplitTransactions++; 0403 break; 0404 } 0405 } 0406 if(singleSplitTransactions > 0 && multipleSplitTransactions == 1) { 0407 pActions[Action::CopySplits]->setEnabled(true); 0408 } 0409 } 0410 } else { 0411 pActions[Action::AssignTransactionsNumber]->setEnabled(d->m_transactionEditor->canAssignNumber()); 0412 pActions[Action::NewTransaction]->setEnabled(false); 0413 pActions[Action::DeleteTransaction]->setEnabled(false); 0414 QString reason; 0415 pActions[Action::EnterTransaction]->setEnabled(d->m_transactionEditor->isComplete(reason)); 0416 //FIXME: Port to KDE4 0417 // the next line somehow worked in KDE3 but does not have 0418 // any influence under KDE4 0419 /// Works for me when 'reason' is set. Allan 0420 pActions[Action::EnterTransaction]->setToolTip(reason); 0421 pActions[Action::CancelTransaction]->setEnabled(true); 0422 } 0423 } 0424 } 0425 0426 void KGlobalLedgerView::slotAboutToSelectItem(KMyMoneyRegister::RegisterItem* item, bool& okToSelect) 0427 { 0428 Q_UNUSED(item); 0429 slotCancelOrEnterTransactions(okToSelect); 0430 } 0431 0432 void KGlobalLedgerView::slotUpdateSummaryLine(const KMyMoneyRegister::SelectedTransactions& selection) 0433 { 0434 Q_D(KGlobalLedgerView); 0435 if (selection.count() > 1) { 0436 MyMoneyMoney balance; 0437 foreach (const KMyMoneyRegister::SelectedTransaction& t, selection) { 0438 if (!t.isScheduled()) { 0439 balance += t.split().shares(); 0440 } 0441 } 0442 d->m_rightSummaryLabel->setText(QString("%1: %2").arg(QChar(0x2211), balance.formatMoney("", -1))); 0443 0444 } else { 0445 if (d->isReconciliationAccount()) { 0446 d->m_rightSummaryLabel->setText(i18n("Difference: %1", d->m_totalBalance.formatMoney("", d->m_precision))); 0447 0448 } else { 0449 if (d->m_currentAccount.accountType() != eMyMoney::Account::Type::Investment) { 0450 d->m_rightSummaryLabel->setText(i18n("Balance: %1", d->m_totalBalance.formatMoney("", d->m_precision))); 0451 bool showNegative = d->m_totalBalance.isNegative(); 0452 if (d->m_currentAccount.accountGroup() == eMyMoney::Account::Type::Liability && !d->m_totalBalance.isZero()) 0453 showNegative = !showNegative; 0454 if (showNegative) { 0455 QPalette palette = d->m_rightSummaryLabel->palette(); 0456 palette.setColor(d->m_rightSummaryLabel->foregroundRole(), KMyMoneySettings::schemeColor(SchemeColor::Negative)); 0457 d->m_rightSummaryLabel->setPalette(palette); 0458 } 0459 } else { 0460 d->m_rightSummaryLabel->setText(i18n("Investment value: %1%2", 0461 d->m_balanceIsApproximated ? "~" : "", 0462 d->m_totalBalance.formatMoney(MyMoneyFile::instance()->baseCurrency().tradingSymbol(), d->m_precision))); 0463 } 0464 } 0465 } 0466 } 0467 0468 void KGlobalLedgerView::resizeEvent(QResizeEvent* ev) 0469 { 0470 if (MyMoneyFile::instance()->storageAttached()) { 0471 Q_D(KGlobalLedgerView); 0472 if (d->m_needLoad) 0473 d->init(); 0474 0475 d->m_register->resize((int)eWidgets::eTransaction::Column::Detail); 0476 d->m_form->resize((int)eWidgets::eTransactionForm::Column::Value1); 0477 } 0478 KMyMoneyViewBase::resizeEvent(ev); 0479 } 0480 0481 void KGlobalLedgerView::slotSetReconcileAccount(const MyMoneyAccount& acc, const QDate& reconciliationDate, const MyMoneyMoney& endingBalance) 0482 { 0483 Q_D(KGlobalLedgerView); 0484 if(d->m_needLoad) 0485 d->init(); 0486 0487 if (d->m_reconciliationAccount.id() != acc.id()) { 0488 // make sure the account is selected 0489 if (!acc.id().isEmpty()) 0490 slotSelectAccount(acc.id()); 0491 0492 d->m_reconciliationAccount = acc; 0493 d->m_reconciliationDate = reconciliationDate; 0494 d->m_endingBalance = endingBalance; 0495 if (acc.accountGroup() == eMyMoney::Account::Type::Liability) 0496 d->m_endingBalance = -endingBalance; 0497 0498 d->m_newAccountLoaded = true; 0499 0500 if (acc.id().isEmpty()) { 0501 d->m_buttonbar->removeAction(pActions[Action::PostponeReconciliation]); 0502 d->m_buttonbar->removeAction(pActions[Action::FinishReconciliation]); 0503 } else { 0504 d->m_buttonbar->addAction(pActions[Action::PostponeReconciliation]); 0505 d->m_buttonbar->addAction(pActions[Action::FinishReconciliation]); 0506 // when we start reconciliation, we need to reload the view 0507 // because no data has been changed. When postponing or finishing 0508 // reconciliation, the data change in the engine takes care of updating 0509 // the view. 0510 refresh(); 0511 } 0512 } 0513 } 0514 0515 void KGlobalLedgerView::slotSetReconcileAccount(const MyMoneyAccount& acc, const QDate& reconciliationDate) 0516 { 0517 slotSetReconcileAccount(acc, reconciliationDate, MyMoneyMoney()); 0518 } 0519 0520 void KGlobalLedgerView::slotSetReconcileAccount(const MyMoneyAccount& acc) 0521 { 0522 slotSetReconcileAccount(acc, QDate(), MyMoneyMoney()); 0523 } 0524 0525 void KGlobalLedgerView::slotSetReconcileAccount() 0526 { 0527 slotSetReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); 0528 } 0529 0530 void KGlobalLedgerView::slotShowTransactionMenu(const MyMoneySplit& sp) 0531 { 0532 Q_UNUSED(sp) 0533 pMenus[Menu::Transaction]->exec(QCursor::pos()); 0534 } 0535 0536 void KGlobalLedgerView::slotContinueReconciliation() 0537 { 0538 Q_D(KGlobalLedgerView); 0539 const auto file = MyMoneyFile::instance(); 0540 MyMoneyAccount account; 0541 0542 try { 0543 account = file->account(d->m_currentAccount.id()); 0544 // get rid of previous run. 0545 delete d->m_endingBalanceDlg; 0546 d->m_endingBalanceDlg = new KEndingBalanceDlg(account, this); 0547 if (account.isAssetLiability()) { 0548 0549 if (d->m_endingBalanceDlg->exec() == QDialog::Accepted) { 0550 if (KMyMoneySettings::autoReconciliation()) { 0551 MyMoneyMoney startBalance = d->m_endingBalanceDlg->previousBalance(); 0552 MyMoneyMoney endBalance = d->m_endingBalanceDlg->endingBalance(); 0553 QDate endDate = d->m_endingBalanceDlg->statementDate(); 0554 0555 QList<QPair<MyMoneyTransaction, MyMoneySplit> > transactionList; 0556 MyMoneyTransactionFilter filter(account.id()); 0557 filter.addState((int)eMyMoney::TransactionFilter::State::Cleared); 0558 filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled); 0559 filter.setDateFilter(QDate(), endDate); 0560 filter.setConsiderCategory(false); 0561 filter.setReportAllSplits(true); 0562 file->transactionList(transactionList, filter); 0563 QList<QPair<MyMoneyTransaction, MyMoneySplit> > result = d->automaticReconciliation(account, transactionList, endBalance - startBalance); 0564 0565 if (!result.empty()) { 0566 QString message = i18n("KMyMoney has detected transactions matching your reconciliation data.\nWould you like KMyMoney to clear these transactions for you?"); 0567 if (KMessageBox::questionYesNo(this, 0568 message, 0569 i18n("Automatic reconciliation"), 0570 KStandardGuiItem::yes(), 0571 KStandardGuiItem::no(), 0572 "AcceptAutomaticReconciliation") == KMessageBox::Yes) { 0573 // mark the transactions cleared 0574 KMyMoneyRegister::SelectedTransactions oldSelection = d->m_selectedTransactions; 0575 d->m_selectedTransactions.clear(); 0576 QListIterator<QPair<MyMoneyTransaction, MyMoneySplit> > itTransactionSplitResult(result); 0577 while (itTransactionSplitResult.hasNext()) { 0578 const QPair<MyMoneyTransaction, MyMoneySplit> &transactionSplit = itTransactionSplitResult.next(); 0579 d->m_selectedTransactions.append(KMyMoneyRegister::SelectedTransaction(transactionSplit.first, transactionSplit.second, QString())); 0580 } 0581 // mark all transactions in d->m_selectedTransactions as 'Cleared' 0582 d->markTransaction(eMyMoney::Split::State::Cleared); 0583 d->m_selectedTransactions = oldSelection; 0584 } 0585 } 0586 } 0587 0588 if (!file->isStandardAccount(account.id()) && 0589 account.isAssetLiability()) { 0590 if (!isVisible()) 0591 emit customActionRequested(View::Ledgers, eView::Action::SwitchView); 0592 Models::instance()->accountsModel()->slotReconcileAccount(account, d->m_endingBalanceDlg->statementDate(), d->m_endingBalanceDlg->endingBalance()); 0593 slotSetReconcileAccount(account, d->m_endingBalanceDlg->statementDate(), d->m_endingBalanceDlg->endingBalance()); 0594 0595 // check if the user requests us to create interest 0596 // or charge transactions. 0597 auto ti = d->m_endingBalanceDlg->interestTransaction(); 0598 auto tc = d->m_endingBalanceDlg->chargeTransaction(); 0599 MyMoneyFileTransaction ft; 0600 try { 0601 if (ti != MyMoneyTransaction()) { 0602 MyMoneyFile::instance()->addTransaction(ti); 0603 } 0604 if (tc != MyMoneyTransaction()) { 0605 MyMoneyFile::instance()->addTransaction(tc); 0606 } 0607 ft.commit(); 0608 0609 } catch (const MyMoneyException &e) { 0610 qWarning("interest transaction not stored: '%s'", e.what()); 0611 } 0612 0613 // reload the account object as it might have changed in the meantime 0614 d->m_reconciliationAccount = file->account(account.id()); 0615 updateActions(d->m_currentAccount); 0616 updateLedgerActionsInternal(); 0617 // slotUpdateActions(); 0618 } 0619 } 0620 } 0621 } catch (const MyMoneyException &) { 0622 } 0623 } 0624 0625 void KGlobalLedgerView::slotLedgerSelected(const QString& _accId, const QString& transaction) 0626 { 0627 auto acc = MyMoneyFile::instance()->account(_accId); 0628 QString accId(_accId); 0629 0630 switch (acc.accountType()) { 0631 case Account::Type::Stock: 0632 // if a stock account is selected, we show the 0633 // the corresponding parent (investment) account 0634 acc = MyMoneyFile::instance()->account(acc.parentAccountId()); 0635 accId = acc.id(); 0636 // intentional fall through 0637 0638 case Account::Type::Checkings: 0639 case Account::Type::Savings: 0640 case Account::Type::Cash: 0641 case Account::Type::CreditCard: 0642 case Account::Type::Loan: 0643 case Account::Type::Asset: 0644 case Account::Type::Liability: 0645 case Account::Type::AssetLoan: 0646 case Account::Type::Income: 0647 case Account::Type::Expense: 0648 case Account::Type::Investment: 0649 case Account::Type::Equity: 0650 if (!isVisible()) 0651 emit customActionRequested(View::Ledgers, eView::Action::SwitchView); 0652 slotSelectAccount(accId, transaction); 0653 break; 0654 0655 case Account::Type::CertificateDep: 0656 case Account::Type::MoneyMarket: 0657 case Account::Type::Currency: 0658 qDebug("No ledger view available for account type %d", (int)acc.accountType()); 0659 break; 0660 0661 default: 0662 qDebug("Unknown account type %d in KMyMoneyView::slotLedgerSelected", (int)acc.accountType()); 0663 break; 0664 } 0665 } 0666 0667 void KGlobalLedgerView::slotSelectByObject(const MyMoneyObject& obj, eView::Intent intent) 0668 { 0669 switch(intent) { 0670 case eView::Intent::UpdateActions: 0671 updateActions(obj); 0672 break; 0673 0674 case eView::Intent::FinishEnteringOverdueScheduledTransactions: 0675 slotContinueReconciliation(); 0676 break; 0677 0678 case eView::Intent::SynchronizeAccountInLedgersView: 0679 slotSelectAccount(obj); 0680 break; 0681 0682 default: 0683 break; 0684 } 0685 } 0686 0687 void KGlobalLedgerView::slotSelectByVariant(const QVariantList& variant, eView::Intent intent) 0688 { 0689 switch(intent) { 0690 case eView::Intent::ShowTransaction: 0691 if (variant.count() == 2) 0692 slotLedgerSelected(variant.at(0).toString(), variant.at(1).toString()); 0693 break; 0694 case eView::Intent::SelectRegisterTransactions: 0695 if (variant.count() == 1) 0696 updateLedgerActions(variant.at(0).value<KMyMoneyRegister::SelectedTransactions>()); 0697 break; 0698 default: 0699 break; 0700 } 0701 } 0702 0703 void KGlobalLedgerView::slotSelectAccount(const MyMoneyObject& obj) 0704 { 0705 Q_D(KGlobalLedgerView); 0706 if (typeid(obj) != typeid(MyMoneyAccount)) 0707 return/* false */; 0708 0709 d->m_lastSelectedAccountID = obj.id(); 0710 } 0711 0712 void KGlobalLedgerView::slotSelectAccount(const QString& id) 0713 { 0714 slotSelectAccount(id, QString()); 0715 } 0716 0717 bool KGlobalLedgerView::slotSelectAccount(const QString& id, const QString& transactionId) 0718 { 0719 Q_D(KGlobalLedgerView); 0720 auto rc = true; 0721 0722 if (!id.isEmpty()) { 0723 if (d->m_currentAccount.id() != id) { 0724 try { 0725 d->m_currentAccount = MyMoneyFile::instance()->account(id); 0726 // if a stock account is selected, we show the 0727 // the corresponding parent (investment) account 0728 if (d->m_currentAccount.isInvest()) { 0729 d->m_currentAccount = MyMoneyFile::instance()->account(d->m_currentAccount.parentAccountId()); 0730 } 0731 d->m_lastSelectedAccountID = d->m_currentAccount.id(); 0732 d->m_newAccountLoaded = true; 0733 refresh(); 0734 } catch (const MyMoneyException &) { 0735 qDebug("Unable to retrieve account %s", qPrintable(id)); 0736 rc = false; 0737 } 0738 } else { 0739 // we need to refresh m_account.m_accountList, a child could have been deleted 0740 d->m_currentAccount = MyMoneyFile::instance()->account(id); 0741 0742 emit selectByObject(d->m_currentAccount, eView::Intent::None); 0743 emit selectByObject(d->m_currentAccount, eView::Intent::SynchronizeAccountInInvestmentView); 0744 } 0745 d->selectTransaction(transactionId); 0746 } 0747 return rc; 0748 } 0749 0750 bool KGlobalLedgerView::selectEmptyTransaction() 0751 { 0752 Q_D(KGlobalLedgerView); 0753 bool rc = false; 0754 0755 if (!d->m_inEditMode) { 0756 // in case we don't know the type of transaction to be created, 0757 // have at least one selected transaction and the id of 0758 // this transaction is not empty, we take it as template for the 0759 // transaction to be created 0760 KMyMoneyRegister::SelectedTransactions list(d->m_register); 0761 if ((d->m_action == eWidgets::eRegister::Action::None) && (!list.isEmpty()) && (!list[0].transaction().id().isEmpty())) { 0762 // the new transaction to be created will have the same type 0763 // as the one that currently has the focus 0764 KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(d->m_register->focusItem()); 0765 if (t) 0766 d->m_action = t->actionType(); 0767 d->m_register->clearSelection(); 0768 } 0769 0770 // if we still don't have an idea which type of transaction 0771 // to create, we use the default. 0772 if (d->m_action == eWidgets::eRegister::Action::None) { 0773 d->setupDefaultAction(); 0774 } 0775 0776 d->m_register->selectItem(d->m_register->lastItem()); 0777 d->m_register->updateRegister(); 0778 rc = true; 0779 } 0780 return rc; 0781 } 0782 0783 TransactionEditor* KGlobalLedgerView::startEdit(const KMyMoneyRegister::SelectedTransactions& list) 0784 { 0785 Q_D(KGlobalLedgerView); 0786 // we use the warnlevel to keep track, if we have to warn the 0787 // user that some or all splits have been reconciled or if the 0788 // user cannot modify the transaction if at least one split 0789 // has the status frozen. The following value are used: 0790 // 0791 // 0 - no sweat, user can modify 0792 // 1 - user should be warned that at least one split has been reconciled 0793 // already 0794 // 2 - editing is not possible, only viewing the transaction is allowed. 0795 // a tooltip shows that this transaction cannot be changed anymore 0796 // 3 - editing is not possible, only viewing the transaction is allowed. 0797 // a tooltip shows that this transaction cannot be changed anymore 0798 0799 int warnLevel = list.warnLevel(); 0800 Q_ASSERT(warnLevel < 4); // otherwise the edit action should not be enabled 0801 0802 if (warnLevel == 1) { 0803 if (KMessageBox::warningContinueCancel(this, 0804 i18n( 0805 "At least one split of the selected transactions has been reconciled. " 0806 "Do you wish to continue to edit the transactions anyway?" 0807 ), 0808 i18n("Transaction already reconciled"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), 0809 "EditReconciledTransaction") == KMessageBox::Cancel) { 0810 d->m_register->endEdit(); 0811 return 0; 0812 } 0813 } 0814 0815 TransactionEditor* editor = 0; 0816 KMyMoneyRegister::Transaction* item = dynamic_cast<KMyMoneyRegister::Transaction*>(d->m_register->focusItem()); 0817 0818 if (item) { 0819 // in case the current focus item is not selected, we move the focus to the first selected transaction 0820 if (!item->isSelected()) { 0821 KMyMoneyRegister::RegisterItem* p; 0822 for (p = d->m_register->firstItem(); p; p = p->nextItem()) { 0823 KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p); 0824 if (t && t->isSelected()) { 0825 d->m_register->setFocusItem(t); 0826 item = t; 0827 break; 0828 } 0829 } 0830 } 0831 0832 // decide, if we edit in the register or in the form 0833 TransactionEditorContainer* parent; 0834 if (d->m_formFrame->isVisible()) 0835 parent = d->m_form; 0836 else { 0837 parent = d->m_register; 0838 } 0839 0840 editor = item->createEditor(parent, list, KGlobalLedgerViewPrivate::m_lastPostDate); 0841 0842 // check that we use the same transaction commodity in all selected transactions 0843 // if not, we need to update this in the editor's list. The user can also bail out 0844 // of this operation which means that we have to stop editing here. 0845 if (editor) { 0846 if (!editor->fixTransactionCommodity(d->m_currentAccount)) { 0847 // if the user wants to quit, we need to destroy the editor 0848 // and bail out 0849 delete editor; 0850 editor = 0; 0851 } 0852 } 0853 0854 if (editor) { 0855 if (parent == d->m_register) { 0856 // make sure, the height of the table is correct 0857 d->m_register->updateRegister(KMyMoneySettings::ledgerLens() | !KMyMoneySettings::transactionForm()); 0858 } 0859 0860 /// possibly switch editor to read-only mode 0861 editor->setReadOnlyMode(warnLevel > 1); 0862 0863 d->m_inEditMode = true; 0864 connect(editor, &TransactionEditor::transactionDataSufficient, pActions[Action::EnterTransaction], &QAction::setEnabled); 0865 connect(editor, &TransactionEditor::returnPressed, pActions[Action::EnterTransaction], &QAction::trigger); 0866 connect(editor, &TransactionEditor::escapePressed, pActions[Action::CancelTransaction], &QAction::trigger); 0867 0868 connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, editor, &TransactionEditor::slotReloadEditWidgets); 0869 connect(editor, &TransactionEditor::finishEdit, this, &KGlobalLedgerView::slotLeaveEditMode); 0870 connect(editor, &TransactionEditor::objectCreation, d->m_mousePressFilter, &MousePressFilter::setFilterDeactive); 0871 connect(editor, &TransactionEditor::lastPostDateUsed, this, &KGlobalLedgerView::slotKeepPostDate); 0872 0873 // create the widgets, place them in the parent and load them with data 0874 // setup tab order 0875 d->m_tabOrderWidgets.clear(); 0876 editor->setup(d->m_tabOrderWidgets, d->m_currentAccount, d->m_action); 0877 0878 Q_ASSERT(!d->m_tabOrderWidgets.isEmpty()); 0879 0880 // install event filter in all taborder widgets 0881 QWidgetList::const_iterator it_w = d->m_tabOrderWidgets.constBegin(); 0882 for (; it_w != d->m_tabOrderWidgets.constEnd(); ++it_w) { 0883 (*it_w)->installEventFilter(this); 0884 } 0885 // Install a filter that checks if a mouse press happened outside 0886 // of one of our own widgets. 0887 qApp->installEventFilter(d->m_mousePressFilter); 0888 0889 // Check if the editor has some preference on where to set the focus 0890 // If not, set the focus to the first widget in the tab order 0891 QWidget* focusWidget = editor->firstWidget(); 0892 if (!focusWidget) 0893 focusWidget = d->m_tabOrderWidgets.first(); 0894 0895 // for some reason, this only works reliably if delayed a bit 0896 QTimer::singleShot(10, focusWidget, SLOT(setFocus())); 0897 0898 // preset to 'I have no idea which type to create' for the next round. 0899 d->m_action = eWidgets::eRegister::Action::None; 0900 } 0901 } 0902 return editor; 0903 } 0904 0905 void KGlobalLedgerView::slotTransactionsContextMenuRequested() 0906 { 0907 Q_D(KGlobalLedgerView); 0908 auto transactions = d->m_selectedTransactions; 0909 updateLedgerActionsInternal(); 0910 // emit transactionsSelected(d->m_selectedTransactions); // that should select MyMoneySchedule in KScheduledView 0911 if (!transactions.isEmpty() && transactions.first().isScheduled()) 0912 emit selectByObject(MyMoneyFile::instance()->schedule(transactions.first().scheduleId()), eView::Intent::OpenContextMenu); 0913 else 0914 slotShowTransactionMenu(MyMoneySplit()); 0915 } 0916 0917 void KGlobalLedgerView::slotLeaveEditMode(const KMyMoneyRegister::SelectedTransactions& list) 0918 { 0919 Q_D(KGlobalLedgerView); 0920 d->m_inEditMode = false; 0921 qApp->removeEventFilter(d->m_mousePressFilter); 0922 0923 // a possible focusOut event may have removed the focus, so we 0924 // install it back again. 0925 d->m_register->focusItem()->setFocus(true); 0926 0927 // if we come back from editing a new item, we make sure that 0928 // we always select the very last known transaction entry no 0929 // matter if the transaction has been created or not. 0930 0931 if (list.count() && list[0].transaction().id().isEmpty()) { 0932 // block signals to prevent some infinite loops that might occur here. 0933 d->m_register->blockSignals(true); 0934 d->m_register->clearSelection(); 0935 KMyMoneyRegister::RegisterItem* p = d->m_register->lastItem(); 0936 if (p && p->prevItem()) 0937 p = p->prevItem(); 0938 d->m_register->selectItem(p); 0939 d->m_register->updateRegister(true); 0940 d->m_register->blockSignals(false); 0941 // we need to update the form manually as sending signals was blocked 0942 KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p); 0943 if (t) 0944 d->m_form->slotSetTransaction(t); 0945 } else { 0946 if (!KMyMoneySettings::transactionForm()) { 0947 // update the row height of the transactions because it might differ between viewing/editing mode when not using the transaction form 0948 d->m_register->blockSignals(true); 0949 d->m_register->updateRegister(true); 0950 d->m_register->blockSignals(false); 0951 } 0952 } 0953 d->m_needsRefresh = true; // TODO: Why transaction in view doesn't update without this? 0954 if (d->m_needsRefresh) 0955 refresh(); 0956 0957 d->m_register->endEdit(); 0958 d->m_register->setFocus(); 0959 } 0960 0961 bool KGlobalLedgerView::focusNextPrevChild(bool next) 0962 { 0963 Q_D(KGlobalLedgerView); 0964 bool rc = false; 0965 // qDebug() << "----------------------------------------------------------"; 0966 // qDebug() << "KGlobalLedgerView::focusNextPrevChild, editmode=" << d->m_inEditMode; 0967 if (d->m_inEditMode) { 0968 QWidget *w = 0; 0969 0970 w = qApp->focusWidget(); 0971 int currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w); 0972 const auto startIndex = currentWidgetIndex; 0973 // qDebug() << "Focus is at currentWidgetIndex" << currentWidgetIndex << w->objectName(); 0974 do { 0975 while (w && currentWidgetIndex == -1) { 0976 // qDebug() << w->objectName() << "not in list, use parent"; 0977 w = w->parentWidget(); 0978 currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w); 0979 } 0980 // qDebug() << "Focus is at currentWidgetIndex" << currentWidgetIndex << w->objectName(); 0981 0982 if (currentWidgetIndex != -1) { 0983 // if(w) qDebug() << "tab order is at" << w->objectName(); 0984 currentWidgetIndex += next ? 1 : -1; 0985 if (currentWidgetIndex < 0) 0986 currentWidgetIndex = d->m_tabOrderWidgets.size() - 1; 0987 else if (currentWidgetIndex >= d->m_tabOrderWidgets.size()) 0988 currentWidgetIndex = 0; 0989 0990 w = d->m_tabOrderWidgets[currentWidgetIndex]; 0991 // qDebug() << "currentWidgetIndex" << currentWidgetIndex << w->objectName() << w->isVisible(); 0992 0993 if (((w->focusPolicy() & Qt::TabFocus) == Qt::TabFocus) && w->isVisible() && w->isEnabled()) { 0994 // qDebug() << "Set focus to" << w->objectName(); 0995 w->setFocus(next ? Qt::TabFocusReason: Qt::BacktabFocusReason); 0996 rc = true; 0997 break; 0998 } 0999 } else { 1000 break; 1001 } 1002 } while(currentWidgetIndex != startIndex); 1003 } else 1004 rc = KMyMoneyViewBase::focusNextPrevChild(next); 1005 return rc; 1006 } 1007 1008 bool KGlobalLedgerView::eventFilter(QObject* o, QEvent* e) 1009 { 1010 Q_D(KGlobalLedgerView); 1011 bool rc = false; 1012 // Need to capture mouse position here as QEvent::ToolTip is too slow 1013 d->m_tooltipPosn = QCursor::pos(); 1014 1015 if (e->type() == QEvent::KeyPress) { 1016 if (d->m_inEditMode) { 1017 // qDebug("object = %s, key = %d", o->className(), k->key()); 1018 if (o == d->m_register) { 1019 // we hide all key press events from the register 1020 // while editing a transaction 1021 rc = true; 1022 } 1023 } 1024 } 1025 1026 if (!rc) 1027 rc = KMyMoneyViewBase::eventFilter(o, e); 1028 1029 return rc; 1030 } 1031 1032 void KGlobalLedgerView::slotSortOptions() 1033 { 1034 Q_D(KGlobalLedgerView); 1035 QPointer<KSortOptionDlg> dlg = new KSortOptionDlg(this); 1036 1037 QString key; 1038 QString sortOrder, def; 1039 if (d->isReconciliationAccount()) { 1040 key = "kmm-sort-reconcile"; 1041 def = KMyMoneySettings::sortReconcileView(); 1042 } else { 1043 key = "kmm-sort-std"; 1044 def = KMyMoneySettings::sortNormalView(); 1045 } 1046 1047 // check if we have an account override of the sort order 1048 if (!d->m_currentAccount.value(key).isEmpty()) 1049 sortOrder = d->m_currentAccount.value(key); 1050 1051 QString oldOrder = sortOrder; 1052 1053 dlg->setSortOption(sortOrder, def); 1054 1055 if (dlg->exec() == QDialog::Accepted) { 1056 if (dlg != 0) { 1057 sortOrder = dlg->sortOption(); 1058 if (sortOrder != oldOrder) { 1059 if (sortOrder.isEmpty()) { 1060 d->m_currentAccount.deletePair(key); 1061 } else { 1062 d->m_currentAccount.setValue(key, sortOrder); 1063 } 1064 MyMoneyFileTransaction ft; 1065 try { 1066 MyMoneyFile::instance()->modifyAccount(d->m_currentAccount); 1067 ft.commit(); 1068 } catch (const MyMoneyException &e) { 1069 qDebug("Unable to update sort order for account '%s': %s", qPrintable(d->m_currentAccount.name()), e.what()); 1070 } 1071 } 1072 } 1073 } 1074 delete dlg; 1075 } 1076 1077 void KGlobalLedgerView::slotToggleTransactionMark(KMyMoneyRegister::Transaction* /* t */) 1078 { 1079 Q_D(KGlobalLedgerView); 1080 if (!d->m_inEditMode) { 1081 slotToggleReconciliationFlag(); 1082 } 1083 } 1084 1085 void KGlobalLedgerView::slotKeepPostDate(const QDate& date) 1086 { 1087 KGlobalLedgerViewPrivate::m_lastPostDate = date; 1088 } 1089 1090 QString KGlobalLedgerView::accountId() const 1091 { 1092 Q_D(const KGlobalLedgerView); 1093 return d->m_currentAccount.id(); 1094 } 1095 1096 bool KGlobalLedgerView::canCreateTransactions(QString& tooltip) const 1097 { 1098 Q_D(const KGlobalLedgerView); 1099 bool rc = true; 1100 1101 if (d->m_currentAccount.id().isEmpty()) { 1102 tooltip = i18n("Cannot create transactions when no account is selected."); 1103 rc = false; 1104 } 1105 if (d->m_currentAccount.accountGroup() == eMyMoney::Account::Type::Income 1106 || d->m_currentAccount.accountGroup() == eMyMoney::Account::Type::Expense) { 1107 tooltip = i18n("Cannot create transactions in the context of a category."); 1108 d->showTooltip(tooltip); 1109 rc = false; 1110 } 1111 if (d->m_currentAccount.isClosed()) { 1112 tooltip = i18n("Cannot create transactions in a closed account."); 1113 d->showTooltip(tooltip); 1114 rc = false; 1115 } 1116 return rc; 1117 } 1118 1119 bool KGlobalLedgerView::canModifyTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const 1120 { 1121 Q_D(const KGlobalLedgerView); 1122 return d->canProcessTransactions(list, tooltip) && list.canModify(); 1123 } 1124 1125 bool KGlobalLedgerView::canDuplicateTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const 1126 { 1127 Q_D(const KGlobalLedgerView); 1128 return d->canProcessTransactions(list, tooltip) && list.canDuplicate(); 1129 } 1130 1131 bool KGlobalLedgerView::canEditTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const 1132 { 1133 Q_D(const KGlobalLedgerView); 1134 // check if we can edit the list of transactions. We can edit, if 1135 // 1136 // a) no mix of standard and investment transactions exist 1137 // b) if a split transaction is selected, this is the only selection 1138 // c) the transaction having the current focus is selected 1139 1140 // check for c) 1141 if (!d->canProcessTransactions(list, tooltip)) 1142 return false; 1143 1144 bool rc = true; 1145 int investmentTransactions = 0; 1146 int normalTransactions = 0; 1147 1148 if (d->m_currentAccount.accountGroup() == eMyMoney::Account::Type::Income 1149 || d->m_currentAccount.accountGroup() == eMyMoney::Account::Type::Expense) { 1150 tooltip = i18n("Cannot edit transactions in the context of a category."); 1151 d->showTooltip(tooltip); 1152 rc = false; 1153 } 1154 1155 // check for a) 1156 KMyMoneyRegister::SelectedTransactions::const_iterator it_t; 1157 QString action; 1158 for (it_t = list.begin(); rc && it_t != list.end(); ++it_t) { 1159 if ((*it_t).transaction().id().isEmpty()) { 1160 tooltip.clear(); 1161 rc = false; 1162 continue; 1163 } 1164 1165 if (KMyMoneyUtils::transactionType((*it_t).transaction()) == KMyMoneyUtils::InvestmentTransaction) { 1166 if (action.isEmpty()) { 1167 action = (*it_t).split().action(); 1168 continue; 1169 } 1170 if (action == (*it_t).split().action()) { 1171 continue; 1172 } else { 1173 tooltip = (i18n("Cannot edit mixed investment action/type transactions together.")); 1174 d->showTooltip(tooltip); 1175 rc = false; 1176 break; 1177 } 1178 } 1179 1180 if (KMyMoneyUtils::transactionType((*it_t).transaction()) == KMyMoneyUtils::InvestmentTransaction) 1181 ++investmentTransactions; 1182 else 1183 ++normalTransactions; 1184 1185 // check for a) 1186 if (investmentTransactions != 0 && normalTransactions != 0) { 1187 tooltip = i18n("Cannot edit investment transactions and non-investment transactions together."); 1188 d->showTooltip(tooltip); 1189 rc = false; 1190 break; 1191 } 1192 1193 // check for b) but only for normalTransactions 1194 if ((*it_t).transaction().splitCount() > 2 && normalTransactions != 0) { 1195 if (list.count() > 1) { 1196 tooltip = i18n("Cannot edit multiple split transactions at once."); 1197 d->showTooltip(tooltip); 1198 rc = false; 1199 break; 1200 } 1201 } 1202 } 1203 1204 // check for multiple transactions being selected in an investment account 1205 // we do not allow editing in this case: https://bugs.kde.org/show_bug.cgi?id=240816 1206 // later on, we might allow to edit investment transactions of the same type 1207 /// Can now disable the following check. 1208 1209 /* if (rc == true && investmentTransactions > 1) { 1210 tooltip = i18n("Cannot edit multiple investment transactions at once"); 1211 rc = false; 1212 }*/ 1213 1214 // now check that we have the correct account type for investment transactions 1215 if (rc == true && investmentTransactions != 0) { 1216 if (d->m_currentAccount.accountType() != eMyMoney::Account::Type::Investment) { 1217 tooltip = i18n("Cannot edit investment transactions in the context of this account."); 1218 rc = false; 1219 } 1220 } 1221 return rc; 1222 } 1223 1224 void KGlobalLedgerView::slotMoveToAccount(const QString& id) 1225 { 1226 Q_D(KGlobalLedgerView); 1227 // close the menu, if it is still open 1228 if (pMenus[Menu::Transaction]->isVisible()) 1229 pMenus[Menu::Transaction]->close(); 1230 1231 if (!d->m_selectedTransactions.isEmpty()) { 1232 const auto file = MyMoneyFile::instance(); 1233 MyMoneyFileTransaction ft; 1234 try { 1235 foreach (const auto selection, d->m_selectedTransactions) { 1236 if (d->m_currentAccount.accountType() == eMyMoney::Account::Type::Investment) { 1237 d->moveInvestmentTransaction(d->m_currentAccount.id(), id, selection.transaction()); 1238 } else { 1239 // we get the data afresh from the engine as 1240 // it might have changed by a previous iteration 1241 // in this loop. Use case: two splits point to 1242 // the same account and both are selected. 1243 auto tid = selection.transaction().id(); 1244 auto sid = selection.split().id(); 1245 auto t = file->transaction(tid); 1246 auto s = t.splitById(sid); 1247 s.setAccountId(id); 1248 t.modifySplit(s); 1249 file->modifyTransaction(t); 1250 } 1251 } 1252 ft.commit(); 1253 } catch (const MyMoneyException &) { 1254 } 1255 } 1256 } 1257 1258 void KGlobalLedgerView::slotUpdateMoveToAccountMenu() 1259 { 1260 Q_D(KGlobalLedgerView); 1261 d->createTransactionMoveMenu(); 1262 1263 // in case we were not able to create the selector, we 1264 // better get out of here. Anything else would cause 1265 // a crash later on (accountSet.load) 1266 if (!d->m_moveToAccountSelector) 1267 return; 1268 1269 if (!d->m_currentAccount.id().isEmpty()) { 1270 AccountSet accountSet; 1271 if (d->m_currentAccount.accountType() == eMyMoney::Account::Type::Investment) { 1272 accountSet.addAccountType(eMyMoney::Account::Type::Investment); 1273 } else if (d->m_currentAccount.isAssetLiability()) { 1274 1275 accountSet.addAccountType(eMyMoney::Account::Type::Checkings); 1276 accountSet.addAccountType(eMyMoney::Account::Type::Savings); 1277 accountSet.addAccountType(eMyMoney::Account::Type::Cash); 1278 accountSet.addAccountType(eMyMoney::Account::Type::AssetLoan); 1279 accountSet.addAccountType(eMyMoney::Account::Type::CertificateDep); 1280 accountSet.addAccountType(eMyMoney::Account::Type::MoneyMarket); 1281 accountSet.addAccountType(eMyMoney::Account::Type::Asset); 1282 accountSet.addAccountType(eMyMoney::Account::Type::Currency); 1283 accountSet.addAccountType(eMyMoney::Account::Type::CreditCard); 1284 accountSet.addAccountType(eMyMoney::Account::Type::Loan); 1285 accountSet.addAccountType(eMyMoney::Account::Type::Liability); 1286 } else if (d->m_currentAccount.isIncomeExpense()) { 1287 accountSet.addAccountType(eMyMoney::Account::Type::Income); 1288 accountSet.addAccountType(eMyMoney::Account::Type::Expense); 1289 } 1290 1291 accountSet.load(d->m_moveToAccountSelector); 1292 // remove those accounts that we currently reference 1293 // with the selected items 1294 foreach (const auto selection, d->m_selectedTransactions) { 1295 d->m_moveToAccountSelector->removeItem(selection.split().accountId()); 1296 } 1297 // remove those accounts from the list that are denominated 1298 // in a different currency 1299 auto list = d->m_moveToAccountSelector->accountList(); 1300 QList<QString>::const_iterator it_a; 1301 for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { 1302 auto acc = MyMoneyFile::instance()->account(*it_a); 1303 if (acc.currencyId() != d->m_currentAccount.currencyId()) 1304 d->m_moveToAccountSelector->removeItem((*it_a)); 1305 } 1306 } 1307 } 1308 1309 void KGlobalLedgerView::slotObjectDestroyed(QObject* o) 1310 { 1311 Q_D(KGlobalLedgerView); 1312 if (o == d->m_moveToAccountSelector) { 1313 d->m_moveToAccountSelector = nullptr; 1314 } 1315 } 1316 1317 void KGlobalLedgerView::slotCancelOrEnterTransactions(bool& okToSelect) 1318 { 1319 Q_D(KGlobalLedgerView); 1320 static bool oneTime = false; 1321 if (!oneTime) { 1322 oneTime = true; 1323 auto dontShowAgain = "CancelOrEditTransaction"; 1324 // qDebug("KMyMoneyApp::slotCancelOrEndEdit"); 1325 if (d->m_transactionEditor) { 1326 if (KMyMoneySettings::focusChangeIsEnter() && pActions[Action::EnterTransaction]->isEnabled()) { 1327 slotEnterTransaction(); 1328 if (d->m_transactionEditor) { 1329 // if at this stage the editor is still there that means that entering the transaction was cancelled 1330 // for example by pressing cancel on the exchange rate editor so we must stay in edit mode 1331 okToSelect = false; 1332 } 1333 } else { 1334 // okToSelect is preset to true if a cancel of the dialog is useful and false if it is not 1335 int rc; 1336 KGuiItem noGuiItem = KStandardGuiItem::save(); 1337 KGuiItem yesGuiItem = KStandardGuiItem::discard(); 1338 KGuiItem cancelGuiItem = KStandardGuiItem::cont(); 1339 1340 // if the transaction can't be entered make sure that it can't be entered by pressing no either 1341 if (!pActions[Action::EnterTransaction]->isEnabled()) { 1342 noGuiItem.setEnabled(false); 1343 noGuiItem.setToolTip(pActions[Action::EnterTransaction]->toolTip()); 1344 } 1345 1346 // in case we have a new transaction and cannot save it we simply cancel 1347 if (!pActions[Action::EnterTransaction]->isEnabled() && d->m_transactionEditor && d->m_transactionEditor->createNewTransaction()) { 1348 rc = KMessageBox::Yes; 1349 1350 } else if (okToSelect == true) { 1351 rc = KMessageBox::warningYesNoCancel(this, i18n("<p>Please select what you want to do: discard the changes, save the changes or continue to edit the transaction.</p><p>You can also set an option to save the transaction automatically when e.g. selecting another transaction.</p>"), i18n("End transaction edit"), yesGuiItem, noGuiItem, cancelGuiItem, dontShowAgain); 1352 1353 } else { 1354 rc = KMessageBox::warningYesNo(this, i18n("<p>Please select what you want to do: discard or save the changes.</p><p>You can also set an option to save the transaction automatically when e.g. selecting another transaction.</p>"), i18n("End transaction edit"), yesGuiItem, noGuiItem, dontShowAgain); 1355 } 1356 1357 switch (rc) { 1358 case KMessageBox::Yes: 1359 slotCancelTransaction(); 1360 break; 1361 case KMessageBox::No: 1362 slotEnterTransaction(); 1363 // make sure that we'll see this message the next time no matter 1364 // if the user has chosen the 'Don't show again' checkbox 1365 KMessageBox::enableMessage(dontShowAgain); 1366 if (d->m_transactionEditor) { 1367 // if at this stage the editor is still there that means that entering the transaction was cancelled 1368 // for example by pressing cancel on the exchange rate editor so we must stay in edit mode 1369 okToSelect = false; 1370 } 1371 break; 1372 case KMessageBox::Cancel: 1373 // make sure that we'll see this message the next time no matter 1374 // if the user has chosen the 'Don't show again' checkbox 1375 KMessageBox::enableMessage(dontShowAgain); 1376 okToSelect = false; 1377 break; 1378 } 1379 } 1380 } 1381 oneTime = false; 1382 } 1383 } 1384 1385 void KGlobalLedgerView::slotNewSchedule(const MyMoneyTransaction& _t, eMyMoney::Schedule::Occurrence occurrence) 1386 { 1387 KEditScheduleDlg::newSchedule(_t, occurrence); 1388 } 1389 1390 void KGlobalLedgerView::slotNewTransactionForm(eWidgets::eRegister::Action id) 1391 { 1392 Q_D(KGlobalLedgerView); 1393 if (!d->m_inEditMode) { 1394 d->m_action = id; 1395 // since we jump here via code, we have to make sure to react only 1396 // if the action is enabled 1397 if (pActions[Action::NewTransaction]->isEnabled()) { 1398 if (d->createNewTransaction()) { 1399 d->m_transactionEditor = d->startEdit(d->m_selectedTransactions); 1400 if (d->m_transactionEditor) { 1401 KMyMoneyMVCCombo::setSubstringSearchForChildren(this/*d->m_myMoneyView*/, !KMyMoneySettings::stringMatchFromStart()); 1402 KMyMoneyPayeeCombo* payeeEdit = dynamic_cast<KMyMoneyPayeeCombo*>(d->m_transactionEditor->haveWidget("payee")); 1403 if (payeeEdit && !d->m_lastPayeeEnteredId.isEmpty()) { 1404 // in case we entered a new transaction before and used a payee, 1405 // we reuse it here. Save the text to the edit widget, select it 1406 // so that hitting any character will start entering another payee. 1407 payeeEdit->setSelectedItem(d->m_lastPayeeEnteredId); 1408 payeeEdit->lineEdit()->selectAll(); 1409 } 1410 if (d->m_transactionEditor) { 1411 connect(d->m_transactionEditor.data(), &TransactionEditor::statusProgress, this, &KGlobalLedgerView::slotStatusProgress); 1412 connect(d->m_transactionEditor.data(), &TransactionEditor::statusMsg, this, &KGlobalLedgerView::slotStatusMsg); 1413 connect(d->m_transactionEditor.data(), &TransactionEditor::scheduleTransaction, this, &KGlobalLedgerView::slotNewSchedule); 1414 } 1415 updateLedgerActionsInternal(); 1416 // emit transactionsSelected(d->m_selectedTransactions); 1417 } 1418 } 1419 } 1420 } 1421 } 1422 1423 void KGlobalLedgerView::slotNewTransaction() 1424 { 1425 // in case the view is not visible ... 1426 if (!isVisible()) { 1427 // we switch to it 1428 pActions[Action::ShowLedgersView]->activate(QAction::ActionEvent::Trigger); 1429 QString tooltip; 1430 if (!canCreateTransactions(tooltip)) { 1431 // and inform the user via a dialog about the reason 1432 // why a transaction cannot be created 1433 KMessageBox::sorry(this, tooltip); 1434 return; 1435 } 1436 } 1437 slotNewTransactionForm(eWidgets::eRegister::Action::None); 1438 } 1439 1440 void KGlobalLedgerView::slotEditTransaction() 1441 { 1442 Q_D(KGlobalLedgerView); 1443 // qDebug("KMyMoneyApp::slotTransactionsEdit()"); 1444 // since we jump here via code, we have to make sure to react only 1445 // if the action is enabled 1446 if (pActions[Action::EditTransaction]->isEnabled()) { 1447 // as soon as we edit a transaction, we don't remember the last payee entered 1448 d->m_lastPayeeEnteredId.clear(); 1449 d->m_transactionEditor = d->startEdit(d->m_selectedTransactions); 1450 KMyMoneyMVCCombo::setSubstringSearchForChildren(this/*d->m_myMoneyView*/, !KMyMoneySettings::stringMatchFromStart()); 1451 updateLedgerActionsInternal(); 1452 } 1453 } 1454 1455 void KGlobalLedgerView::slotDeleteTransaction() 1456 { 1457 Q_D(KGlobalLedgerView); 1458 // since we may jump here via code, we have to make sure to react only 1459 // if the action is enabled 1460 if (!pActions[Action::DeleteTransaction]->isEnabled()) 1461 return; 1462 if (d->m_selectedTransactions.isEmpty()) 1463 return; 1464 if (d->m_selectedTransactions.warnLevel() == 1) { 1465 if (KMessageBox::warningContinueCancel(this, 1466 i18n("At least one split of the selected transactions has been reconciled. " 1467 "Do you wish to delete the transactions anyway?"), 1468 i18n("Transaction already reconciled")) == KMessageBox::Cancel) 1469 return; 1470 } 1471 auto msg = 1472 i18np("Do you really want to delete the selected transaction?", 1473 "Do you really want to delete all %1 selected transactions?", 1474 d->m_selectedTransactions.count()); 1475 1476 if (KMessageBox::questionYesNo(this, msg, i18n("Delete transaction")) == KMessageBox::Yes) { 1477 //KMSTATUS(i18n("Deleting transactions")); 1478 d->doDeleteTransactions(); 1479 } 1480 } 1481 1482 void KGlobalLedgerView::slotDuplicateTransaction(bool reverse) 1483 { 1484 Q_D(KGlobalLedgerView); 1485 // since we may jump here via code, we have to make sure to react only 1486 // if the action is enabled 1487 if (pActions[Action::DuplicateTransaction]->isEnabled()) { 1488 KMyMoneyRegister::SelectedTransactions selectionList = d->m_selectedTransactions; 1489 KMyMoneyRegister::SelectedTransactions::iterator it_t; 1490 1491 int i = 0; 1492 int cnt = d->m_selectedTransactions.count(); 1493 // KMSTATUS(i18n("Duplicating transactions")); 1494 emit selectByVariant(QVariantList {QVariant(0), QVariant(cnt)}, eView::Intent::ReportProgress); 1495 MyMoneyFileTransaction ft; 1496 MyMoneyTransaction lt; 1497 try { 1498 foreach (const auto selection, selectionList) { 1499 auto t = selection.transaction(); 1500 // wipe out any reconciliation information 1501 for (auto& split : t.splits()) { 1502 split.setReconcileFlag(eMyMoney::Split::State::NotReconciled); 1503 split.setReconcileDate(QDate()); 1504 split.setBankID(QString()); 1505 split.removeMatch(); 1506 } 1507 // clear invalid data 1508 t.setEntryDate(QDate()); 1509 t.clearId(); 1510 1511 if (reverse) 1512 // reverse transaction 1513 t.reverse(); 1514 else 1515 // set the post date to today 1516 t.setPostDate(QDate::currentDate()); 1517 1518 MyMoneyFile::instance()->addTransaction(t); 1519 lt = t; 1520 emit selectByVariant(QVariantList {QVariant(i++), QVariant(0)}, eView::Intent::ReportProgress); 1521 } 1522 ft.commit(); 1523 1524 // select the new transaction in the ledger 1525 if (!d->m_currentAccount.id().isEmpty()) 1526 slotLedgerSelected(d->m_currentAccount.id(), lt.id()); 1527 } catch (const MyMoneyException &e) { 1528 KMessageBox::detailedSorry(this, i18n("Unable to duplicate transaction(s)"), QString::fromLatin1(e.what())); 1529 } 1530 // switch off the progress bar 1531 emit selectByVariant(QVariantList {QVariant(-1), QVariant(-1)}, eView::Intent::ReportProgress); 1532 } 1533 } 1534 1535 void KGlobalLedgerView::slotEnterTransaction() 1536 { 1537 Q_D(KGlobalLedgerView); 1538 // since we jump here via code, we have to make sure to react only 1539 // if the action is enabled 1540 if (pActions[Action::EnterTransaction]->isEnabled()) { 1541 // disable the action while we process it to make sure it's processed only once since 1542 // d->m_transactionEditor->enterTransactions(newId) will run QCoreApplication::processEvents 1543 // we could end up here twice which will cause a crash slotUpdateActions() will enable the action again 1544 pActions[Action::EnterTransaction]->setEnabled(false); 1545 if (d->m_transactionEditor) { 1546 QString accountId = d->m_currentAccount.id(); 1547 QString newId; 1548 connect(d->m_transactionEditor.data(), &TransactionEditor::balanceWarning, d->m_balanceWarning.data(), &KBalanceWarning::slotShowMessage); 1549 if (d->m_transactionEditor->enterTransactions(newId)) { 1550 KMyMoneyPayeeCombo* payeeEdit = dynamic_cast<KMyMoneyPayeeCombo*>(d->m_transactionEditor->haveWidget("payee")); 1551 if (payeeEdit && !newId.isEmpty()) { 1552 d->m_lastPayeeEnteredId = payeeEdit->selectedItem(); 1553 } 1554 d->deleteTransactionEditor(); 1555 } 1556 if (!newId.isEmpty()) { 1557 slotLedgerSelected(accountId, newId); 1558 } 1559 } 1560 updateLedgerActionsInternal(); 1561 } 1562 } 1563 1564 void KGlobalLedgerView::slotAcceptTransaction() 1565 { 1566 Q_D(KGlobalLedgerView); 1567 KMyMoneyRegister::SelectedTransactions list = d->m_selectedTransactions; 1568 KMyMoneyRegister::SelectedTransactions::const_iterator it_t; 1569 int cnt = list.count(); 1570 int i = 0; 1571 emit selectByVariant(QVariantList {QVariant(0), QVariant(cnt)}, eView::Intent::ReportProgress); 1572 MyMoneyFileTransaction ft; 1573 try { 1574 for (it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) { 1575 // reload transaction in case it got changed during the course of this loop 1576 MyMoneyTransaction t = MyMoneyFile::instance()->transaction((*it_t).transaction().id()); 1577 if (t.isImported()) { 1578 t.setImported(false); 1579 if (!d->m_currentAccount.id().isEmpty()) { 1580 foreach (const auto split, t.splits()) { 1581 if (split.accountId() == d->m_currentAccount.id()) { 1582 if (split.reconcileFlag() == eMyMoney::Split::State::NotReconciled) { 1583 MyMoneySplit s = split; 1584 s.setReconcileFlag(eMyMoney::Split::State::Cleared); 1585 t.modifySplit(s); 1586 } 1587 } 1588 } 1589 } 1590 MyMoneyFile::instance()->modifyTransaction(t); 1591 } 1592 if ((*it_t).split().isMatched()) { 1593 // reload split in case it got changed during the course of this loop 1594 MyMoneySplit s = t.splitById((*it_t).split().id()); 1595 TransactionMatcher matcher(d->m_currentAccount); 1596 matcher.accept(t, s); 1597 } 1598 emit selectByVariant(QVariantList {QVariant(i++), QVariant(0)}, eView::Intent::ReportProgress); 1599 } 1600 emit selectByVariant(QVariantList {QVariant(-1), QVariant(-1)}, eView::Intent::ReportProgress); 1601 ft.commit(); 1602 } catch (const MyMoneyException &e) { 1603 KMessageBox::detailedSorry(this, i18n("Unable to accept transaction"), QString::fromLatin1(e.what())); 1604 } 1605 } 1606 1607 void KGlobalLedgerView::slotCancelTransaction() 1608 { 1609 Q_D(KGlobalLedgerView); 1610 // since we jump here via code, we have to make sure to react only 1611 // if the action is enabled 1612 if (pActions[Action::CancelTransaction]->isEnabled()) { 1613 // make sure, we block the enter function 1614 pActions[Action::EnterTransaction]->setEnabled(false); 1615 // qDebug("KMyMoneyApp::slotTransactionsCancel"); 1616 d->deleteTransactionEditor(); 1617 updateLedgerActions(d->m_selectedTransactions); 1618 emit selectByVariant(QVariantList {QVariant::fromValue(d->m_selectedTransactions)}, eView::Intent::SelectRegisterTransactions); 1619 } 1620 } 1621 1622 void KGlobalLedgerView::slotEditSplits() 1623 { 1624 Q_D(KGlobalLedgerView); 1625 // since we jump here via code, we have to make sure to react only 1626 // if the action is enabled 1627 if (pActions[Action::EditSplits]->isEnabled()) { 1628 // as soon as we edit a transaction, we don't remember the last payee entered 1629 d->m_lastPayeeEnteredId.clear(); 1630 d->m_transactionEditor = d->startEdit(d->m_selectedTransactions); 1631 updateLedgerActions(d->m_selectedTransactions); 1632 emit selectByVariant(QVariantList {QVariant::fromValue(d->m_selectedTransactions)}, eView::Intent::SelectRegisterTransactions); 1633 1634 if (d->m_transactionEditor) { 1635 KMyMoneyMVCCombo::setSubstringSearchForChildren(this/*d->m_myMoneyView*/, !KMyMoneySettings::stringMatchFromStart()); 1636 if (d->m_transactionEditor->slotEditSplits() == QDialog::Accepted) { 1637 MyMoneyFileTransaction ft; 1638 try { 1639 QString id; 1640 connect(d->m_transactionEditor.data(), &TransactionEditor::balanceWarning, d->m_balanceWarning.data(), &KBalanceWarning::slotShowMessage); 1641 d->m_transactionEditor->enterTransactions(id); 1642 ft.commit(); 1643 } catch (const MyMoneyException &e) { 1644 KMessageBox::detailedSorry(this, i18n("Unable to modify transaction"), QString::fromLatin1(e.what())); 1645 } 1646 } 1647 } 1648 d->deleteTransactionEditor(); 1649 updateLedgerActions(d->m_selectedTransactions); 1650 emit selectByVariant(QVariantList {QVariant::fromValue(d->m_selectedTransactions)}, eView::Intent::SelectRegisterTransactions); 1651 } 1652 } 1653 1654 void KGlobalLedgerView::slotCopyTransactionToClipboard() 1655 { 1656 Q_D(KGlobalLedgerView); 1657 1658 // suppress copy transactions if view not visible 1659 // or in edit mode 1660 if (!isVisible() || d->m_inEditMode) 1661 return; 1662 1663 // format transactions into text 1664 QString txt; 1665 const auto file = MyMoneyFile::instance(); 1666 const auto acc = file->account(d->m_lastSelectedAccountID); 1667 const auto currency = file->currency(acc.currencyId()); 1668 1669 foreach (const auto& st, d->m_selectedTransactions) { 1670 if (!txt.isEmpty() || (d->m_selectedTransactions.count() > 1)) { 1671 txt += QStringLiteral("----------------------------\n"); 1672 } 1673 try { 1674 const auto& s = st.split(); 1675 // Date 1676 txt += i18n("Date: %1", st.transaction().postDate().toString(Qt::DefaultLocaleShortDate)); 1677 txt += QStringLiteral("\n"); 1678 // Payee 1679 QString payee = i18nc("Name for unknown payee", "Unknown"); 1680 if (!s.payeeId().isEmpty()) { 1681 payee = file->payee(s.payeeId()).name(); 1682 } 1683 txt += i18n("Payee: %1", payee); 1684 txt += QStringLiteral("\n"); 1685 // Amount 1686 txt += i18n("Amount: %1", s.value().formatMoney(currency.tradingSymbol(), MyMoneyMoney::denomToPrec(acc.fraction(currency)))); 1687 txt += QStringLiteral("\n"); 1688 // Memo 1689 txt += i18n("Memo: %1", s.memo()); 1690 txt += QStringLiteral("\n"); 1691 1692 } catch (MyMoneyException &) { 1693 qDebug() << "Cannot copy transaction" << st.transaction().id() << "to clipboard"; 1694 } 1695 } 1696 if (d->m_selectedTransactions.count() > 1) { 1697 txt += QStringLiteral("----------------------------\n"); 1698 } 1699 1700 if (!txt.isEmpty()) { 1701 QClipboard *clipboard = QGuiApplication::clipboard(); 1702 clipboard->setText(txt); 1703 } 1704 } 1705 1706 void KGlobalLedgerView::slotCopySplits() 1707 { 1708 Q_D(KGlobalLedgerView); 1709 const auto file = MyMoneyFile::instance(); 1710 1711 if (d->m_selectedTransactions.count() >= 2) { 1712 int singleSplitTransactions = 0; 1713 int multipleSplitTransactions = 0; 1714 KMyMoneyRegister::SelectedTransaction selectedSourceTransaction; 1715 foreach (const auto& st, d->m_selectedTransactions) { 1716 switch (st.transaction().splitCount()) { 1717 case 0: 1718 break; 1719 case 1: 1720 singleSplitTransactions++; 1721 break; 1722 default: 1723 selectedSourceTransaction = st; 1724 multipleSplitTransactions++; 1725 break; 1726 } 1727 } 1728 if (singleSplitTransactions > 0 && multipleSplitTransactions == 1) { 1729 MyMoneyFileTransaction ft; 1730 try { 1731 const auto& sourceTransaction = selectedSourceTransaction.transaction(); 1732 const auto& sourceSplit = selectedSourceTransaction.split(); 1733 foreach (const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) { 1734 auto t = st.transaction(); 1735 1736 // don't process the source transaction 1737 if (sourceTransaction.id() == t.id()) { 1738 continue; 1739 } 1740 1741 const auto& baseSplit = st.split(); 1742 1743 if (t.splitCount() == 1) { 1744 foreach (const auto& split, sourceTransaction.splits()) { 1745 // Don't copy the source split, as we already have that 1746 // as part of the destination transaction 1747 if (split.id() == sourceSplit.id()) { 1748 continue; 1749 } 1750 1751 MyMoneySplit sp(split); 1752 // clear the ID and reconciliation state 1753 sp.clearId(); 1754 sp.setReconcileFlag(eMyMoney::Split::State::NotReconciled); 1755 sp.setReconcileDate(QDate()); 1756 1757 // in case it is a simple transaction consisting of two splits, 1758 // we can adjust the share and value part of the second split we 1759 // just created. We need to keep a possible price in mind in case 1760 // of different currencies 1761 if (sourceTransaction.splitCount() == 2) { 1762 sp.setValue(-baseSplit.value()); 1763 sp.setShares(-(baseSplit.shares() * baseSplit.price())); 1764 } 1765 t.addSplit(sp); 1766 } 1767 // check if we need to add/update a VAT assignment 1768 file->updateVAT(t); 1769 1770 // and store the modified transaction 1771 file->modifyTransaction(t); 1772 } 1773 } 1774 ft.commit(); 1775 } catch (const MyMoneyException &) { 1776 qDebug() << "transactionCopySplits() failed"; 1777 } 1778 } 1779 } 1780 } 1781 1782 void KGlobalLedgerView::slotGoToPayee() 1783 { 1784 Q_D(KGlobalLedgerView); 1785 if (!d->m_payeeGoto.isEmpty()) { 1786 try { 1787 QString transactionId; 1788 if (d->m_selectedTransactions.count() == 1) { 1789 transactionId = d->m_selectedTransactions[0].transaction().id(); 1790 } 1791 // make sure to pass copies, as d->myMoneyView->slotPayeeSelected() overrides 1792 // d->m_payeeGoto and d->m_currentAccount while calling slotUpdateActions() 1793 QString payeeId = d->m_payeeGoto; 1794 QString accountId = d->m_currentAccount.id(); 1795 emit selectByVariant(QVariantList {QVariant(payeeId), QVariant(accountId), QVariant(transactionId)}, eView::Intent::ShowPayee); 1796 // emit openPayeeRequested(payeeId, accountId, transactionId); 1797 } catch (const MyMoneyException &) { 1798 } 1799 } 1800 } 1801 1802 void KGlobalLedgerView::slotGoToAccount() 1803 { 1804 Q_D(KGlobalLedgerView); 1805 if (!d->m_accountGoto.isEmpty()) { 1806 try { 1807 QString transactionId; 1808 if (d->m_selectedTransactions.count() == 1) { 1809 transactionId = d->m_selectedTransactions[0].transaction().id(); 1810 } 1811 // make sure to pass a copy, as d->myMoneyView->slotLedgerSelected() overrides 1812 // d->m_accountGoto while calling slotUpdateActions() 1813 slotLedgerSelected(d->m_accountGoto, transactionId); 1814 } catch (const MyMoneyException &) { 1815 } 1816 } 1817 } 1818 1819 void KGlobalLedgerView::slotMatchTransactions() 1820 { 1821 Q_D(KGlobalLedgerView); 1822 // if the menu action is retrieved it can contain an '&' character for the accelerator causing the comparison to fail if not removed 1823 QString transactionActionText = pActions[Action::MatchTransaction]->text(); 1824 transactionActionText.remove('&'); 1825 if (transactionActionText == i18nc("Button text for match transaction", "Match")) 1826 d->transactionMatch(); 1827 else 1828 d->transactionUnmatch(); 1829 } 1830 1831 void KGlobalLedgerView::slotCombineTransactions() 1832 { 1833 qDebug("slotTransactionCombine() not implemented yet"); 1834 } 1835 1836 void KGlobalLedgerView::slotToggleReconciliationFlag() 1837 { 1838 Q_D(KGlobalLedgerView); 1839 d->markTransaction(eMyMoney::Split::State::Unknown); 1840 } 1841 1842 void KGlobalLedgerView::slotMarkCleared() 1843 { 1844 Q_D(KGlobalLedgerView); 1845 d->markTransaction(eMyMoney::Split::State::Cleared); 1846 } 1847 1848 void KGlobalLedgerView::slotMarkReconciled() 1849 { 1850 Q_D(KGlobalLedgerView); 1851 d->markTransaction(eMyMoney::Split::State::Reconciled); 1852 } 1853 1854 void KGlobalLedgerView::slotMarkNotReconciled() 1855 { 1856 Q_D(KGlobalLedgerView); 1857 d->markTransaction(eMyMoney::Split::State::NotReconciled); 1858 } 1859 1860 void KGlobalLedgerView::slotSelectAllTransactions() 1861 { 1862 Q_D(KGlobalLedgerView); 1863 if(d->m_needLoad) 1864 d->init(); 1865 1866 d->m_register->clearSelection(); 1867 KMyMoneyRegister::RegisterItem* p = d->m_register->firstItem(); 1868 while (p) { 1869 KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p); 1870 if (t) { 1871 if (t->isVisible() && t->isSelectable() && !t->isScheduled() && !t->id().isEmpty()) { 1872 t->setSelected(true); 1873 } 1874 } 1875 p = p->nextItem(); 1876 } 1877 // this is here only to re-paint the items without selecting anything because the data (including the selection) is not really held in the model right now 1878 d->m_register->selectAll(); 1879 1880 // inform everyone else about the selected items 1881 KMyMoneyRegister::SelectedTransactions list(d->m_register); 1882 updateLedgerActions(list); 1883 emit selectByVariant(QVariantList {QVariant::fromValue(list)}, eView::Intent::SelectRegisterTransactions); 1884 } 1885 1886 void KGlobalLedgerView::slotCreateScheduledTransaction() 1887 { 1888 Q_D(KGlobalLedgerView); 1889 if (d->m_selectedTransactions.count() == 1) { 1890 // make sure to have the current selected split as first split in the schedule 1891 MyMoneyTransaction t = d->m_selectedTransactions[0].transaction(); 1892 MyMoneySplit s = d->m_selectedTransactions[0].split(); 1893 QString splitId = s.id(); 1894 s.clearId(); 1895 s.setReconcileFlag(eMyMoney::Split::State::NotReconciled); 1896 s.setReconcileDate(QDate()); 1897 t.removeSplits(); 1898 t.addSplit(s); 1899 foreach (const auto split, d->m_selectedTransactions[0].transaction().splits()) { 1900 if (split.id() != splitId) { 1901 MyMoneySplit s0 = split; 1902 s0.clearId(); 1903 s0.setReconcileFlag(eMyMoney::Split::State::NotReconciled); 1904 s0.setReconcileDate(QDate()); 1905 t.addSplit(s0); 1906 } 1907 } 1908 KEditScheduleDlg::newSchedule(t, eMyMoney::Schedule::Occurrence::Monthly); 1909 } 1910 } 1911 1912 void KGlobalLedgerView::slotAssignNumber() 1913 { 1914 Q_D(KGlobalLedgerView); 1915 if (d->m_transactionEditor) 1916 d->m_transactionEditor->assignNextNumber(); 1917 } 1918 1919 void KGlobalLedgerView::slotStartReconciliation() 1920 { 1921 Q_D(KGlobalLedgerView); 1922 1923 // we cannot reconcile standard accounts 1924 if (!MyMoneyFile::instance()->isStandardAccount(d->m_currentAccount.id())) { 1925 emit selectByObject(d->m_currentAccount, eView::Intent::SynchronizeAccountInLedgersView); 1926 emit selectByObject(d->m_currentAccount, eView::Intent::StartEnteringOverdueScheduledTransactions); 1927 // asynchronous call to KScheduledView::slotEnterOverdueSchedules is made here 1928 // after that all activity should be continued in KGlobalLedgerView::slotContinueReconciliation() 1929 } 1930 } 1931 1932 void KGlobalLedgerView::slotFinishReconciliation() 1933 { 1934 Q_D(KGlobalLedgerView); 1935 const auto file = MyMoneyFile::instance(); 1936 1937 if (!d->m_reconciliationAccount.id().isEmpty()) { 1938 // retrieve list of all transactions that are not reconciled or cleared 1939 QList<QPair<MyMoneyTransaction, MyMoneySplit> > transactionList; 1940 MyMoneyTransactionFilter filter(d->m_reconciliationAccount.id()); 1941 filter.addState((int)eMyMoney::TransactionFilter::State::Cleared); 1942 filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled); 1943 filter.setDateFilter(QDate(), d->m_endingBalanceDlg->statementDate()); 1944 filter.setConsiderCategory(false); 1945 filter.setReportAllSplits(true); 1946 file->transactionList(transactionList, filter); 1947 1948 auto balance = MyMoneyFile::instance()->balance(d->m_reconciliationAccount.id(), d->m_endingBalanceDlg->statementDate()); 1949 MyMoneyMoney actBalance, clearedBalance; 1950 actBalance = clearedBalance = balance; 1951 1952 // walk the list of transactions to figure out the balance(s) 1953 for (auto it = transactionList.constBegin(); it != transactionList.constEnd(); ++it) { 1954 if ((*it).second.reconcileFlag() == eMyMoney::Split::State::NotReconciled) { 1955 clearedBalance -= (*it).second.shares(); 1956 } 1957 } 1958 1959 if (d->m_endingBalanceDlg->endingBalance() != clearedBalance) { 1960 auto message = i18n("You are about to finish the reconciliation of this account with a difference between your bank statement and the transactions marked as cleared.\n" 1961 "Are you sure you want to finish the reconciliation?"); 1962 if (KMessageBox::questionYesNo(this, message, i18n("Confirm end of reconciliation"), KStandardGuiItem::yes(), KStandardGuiItem::no()) == KMessageBox::No) 1963 return; 1964 } 1965 1966 MyMoneyFileTransaction ft; 1967 1968 // refresh object 1969 d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id()); 1970 1971 // Turn off reconciliation mode 1972 // Models::instance()->accountsModel()->slotReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); 1973 // slotSetReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); 1974 // d->m_myMoneyView->finishReconciliation(d->m_reconciliationAccount); 1975 1976 // only update the last statement balance here, if we haven't a newer one due 1977 // to download of online statements. 1978 if (d->m_reconciliationAccount.value("lastImportedTransactionDate").isEmpty() 1979 || QDate::fromString(d->m_reconciliationAccount.value("lastImportedTransactionDate"), Qt::ISODate) < d->m_endingBalanceDlg->statementDate()) { 1980 d->m_reconciliationAccount.setValue("lastStatementBalance", d->m_endingBalanceDlg->endingBalance().toString()); 1981 // in case we override the last statement balance here, we have to make sure 1982 // that we don't show the online balance anymore, as it might be different 1983 d->m_reconciliationAccount.deletePair("lastImportedTransactionDate"); 1984 } 1985 d->m_reconciliationAccount.setLastReconciliationDate(d->m_endingBalanceDlg->statementDate()); 1986 1987 // keep a record of this reconciliation 1988 d->m_reconciliationAccount.addReconciliation(d->m_endingBalanceDlg->statementDate(), d->m_endingBalanceDlg->endingBalance()); 1989 1990 d->m_reconciliationAccount.deletePair("lastReconciledBalance"); 1991 d->m_reconciliationAccount.deletePair("statementBalance"); 1992 d->m_reconciliationAccount.deletePair("statementDate"); 1993 1994 try { 1995 // update the account data 1996 file->modifyAccount(d->m_reconciliationAccount); 1997 1998 /* 1999 // collect the list of cleared splits for this account 2000 filter.clear(); 2001 filter.addAccount(d->m_reconciliationAccount.id()); 2002 filter.addState(eMyMoney::TransactionFilter::Cleared); 2003 filter.setConsiderCategory(false); 2004 filter.setReportAllSplits(true); 2005 file->transactionList(transactionList, filter); 2006 */ 2007 2008 // walk the list of transactions/splits and mark the cleared ones as reconciled 2009 2010 for (auto it = transactionList.begin(); it != transactionList.end(); ++it) { 2011 MyMoneySplit sp = (*it).second; 2012 // skip the ones that are not marked cleared 2013 if (sp.reconcileFlag() != eMyMoney::Split::State::Cleared) 2014 continue; 2015 2016 // always retrieve a fresh copy of the transaction because we 2017 // might have changed it already with another split 2018 MyMoneyTransaction t = file->transaction((*it).first.id()); 2019 sp.setReconcileFlag(eMyMoney::Split::State::Reconciled); 2020 sp.setReconcileDate(d->m_endingBalanceDlg->statementDate()); 2021 t.modifySplit(sp); 2022 2023 // update the engine ... 2024 file->modifyTransaction(t); 2025 2026 // ... and the list 2027 (*it) = qMakePair(t, sp); 2028 } 2029 ft.commit(); 2030 2031 // reload account data from engine as the data might have changed in the meantime 2032 d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id()); 2033 2034 /** 2035 * This signal is emitted when an account has been successfully reconciled 2036 * and all transactions are updated in the engine. It can be used by plugins 2037 * to create reconciliation reports. 2038 * 2039 * @param account the account data 2040 * @param date the reconciliation date as provided through the dialog 2041 * @param startingBalance the starting balance as provided through the dialog 2042 * @param endingBalance the ending balance as provided through the dialog 2043 * @param transactionList reference to QList of QPair containing all 2044 * transaction/split pairs processed by the reconciliation. 2045 */ 2046 emit selectByVariant(QVariantList { 2047 QVariant::fromValue(d->m_reconciliationAccount), 2048 QVariant::fromValue(d->m_endingBalanceDlg->statementDate()), 2049 QVariant::fromValue(d->m_endingBalanceDlg->previousBalance()), 2050 QVariant::fromValue(d->m_endingBalanceDlg->endingBalance()), 2051 QVariant::fromValue(transactionList) 2052 }, eView::Intent::AccountReconciled); 2053 2054 } catch (const MyMoneyException &) { 2055 qDebug("Unexpected exception when setting cleared to reconcile"); 2056 } 2057 // Turn off reconciliation mode 2058 Models::instance()->accountsModel()->slotReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); 2059 slotSetReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); 2060 } 2061 // Turn off reconciliation mode 2062 d->m_reconciliationAccount = MyMoneyAccount(); 2063 updateActions(d->m_currentAccount); 2064 updateLedgerActionsInternal(); 2065 d->loadView(); 2066 // slotUpdateActions(); 2067 } 2068 2069 void KGlobalLedgerView::slotPostponeReconciliation() 2070 { 2071 Q_D(KGlobalLedgerView); 2072 MyMoneyFileTransaction ft; 2073 const auto file = MyMoneyFile::instance(); 2074 2075 if (!d->m_reconciliationAccount.id().isEmpty()) { 2076 // refresh object 2077 d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id()); 2078 2079 // Turn off reconciliation mode 2080 // Models::instance()->accountsModel()->slotReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); 2081 // slotSetReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); 2082 // d->m_myMoneyView->finishReconciliation(d->m_reconciliationAccount); 2083 2084 d->m_reconciliationAccount.setValue("lastReconciledBalance", d->m_endingBalanceDlg->previousBalance().toString()); 2085 d->m_reconciliationAccount.setValue("statementBalance", d->m_endingBalanceDlg->endingBalance().toString()); 2086 d->m_reconciliationAccount.setValue("statementDate", d->m_endingBalanceDlg->statementDate().toString(Qt::ISODate)); 2087 2088 try { 2089 file->modifyAccount(d->m_reconciliationAccount); 2090 ft.commit(); 2091 d->m_reconciliationAccount = MyMoneyAccount(); 2092 updateActions(d->m_currentAccount); 2093 updateLedgerActionsInternal(); 2094 // slotUpdateActions(); 2095 } catch (const MyMoneyException &) { 2096 qDebug("Unexpected exception when setting last reconcile info into account"); 2097 ft.rollback(); 2098 d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id()); 2099 } 2100 // Turn off reconciliation mode 2101 Models::instance()->accountsModel()->slotReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); 2102 slotSetReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); 2103 d->loadView(); 2104 } 2105 } 2106 2107 void KGlobalLedgerView::slotOpenAccount() 2108 { 2109 Q_D(KGlobalLedgerView); 2110 if (!MyMoneyFile::instance()->isStandardAccount(d->m_currentAccount.id())) 2111 slotLedgerSelected(d->m_currentAccount.id(), QString()); 2112 } 2113 2114 void KGlobalLedgerView::slotFindTransaction() 2115 { 2116 Q_D(KGlobalLedgerView); 2117 if (!d->m_searchDlg) { 2118 d->m_searchDlg = new KFindTransactionDlg(this); 2119 connect(d->m_searchDlg, &QObject::destroyed, this, &KGlobalLedgerView::slotCloseSearchDialog); 2120 connect(d->m_searchDlg, &KFindTransactionDlg::transactionSelected, 2121 this, &KGlobalLedgerView::slotLedgerSelected); 2122 } 2123 d->m_searchDlg->show(); 2124 d->m_searchDlg->raise(); 2125 d->m_searchDlg->activateWindow(); 2126 } 2127 2128 void KGlobalLedgerView::slotCloseSearchDialog() 2129 { 2130 Q_D(KGlobalLedgerView); 2131 if (d->m_searchDlg) 2132 d->m_searchDlg->deleteLater(); 2133 d->m_searchDlg = nullptr; 2134 } 2135 2136 void KGlobalLedgerView::slotStatusMsg(const QString& txt) 2137 { 2138 emit selectByVariant(QVariantList {QVariant(txt)}, eView::Intent::ReportProgressMessage); 2139 } 2140 2141 void KGlobalLedgerView::slotStatusProgress(int cnt, int base) 2142 { 2143 emit selectByVariant(QVariantList {QVariant(cnt), QVariant(base)}, eView::Intent::ReportProgress); 2144 } 2145 2146 void KGlobalLedgerView::slotTransactionsSelected(const KMyMoneyRegister::SelectedTransactions& list) 2147 { 2148 updateLedgerActions(list); 2149 emit selectByVariant(QVariantList {QVariant::fromValue(list)}, eView::Intent::SelectRegisterTransactions); 2150 }