File indexing completed on 2024-05-12 16:42:12
0001 /* 0002 SPDX-FileCopyrightText: 2007-2018 Thomas Baumgart <tbaumgart@kde.org> 0003 SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "transactioneditor.h" 0008 #include "transactioneditor_p.h" 0009 0010 // ---------------------------------------------------------------------------- 0011 // QT Includes 0012 0013 #include <QApplication> 0014 #include <QEventLoop> 0015 #include <QKeyEvent> 0016 #include <QList> 0017 #include <QEvent> 0018 #include <QIcon> 0019 #include <QTimer> 0020 0021 // ---------------------------------------------------------------------------- 0022 // KDE Includes 0023 0024 #include <KTextEdit> 0025 #include <KLocalizedString> 0026 #include <KMessageBox> 0027 #include <KStandardGuiItem> 0028 #include <KGuiItem> 0029 0030 // ---------------------------------------------------------------------------- 0031 // Project Includes 0032 0033 #include "kmymoneytagcombo.h" 0034 #include "knewinvestmentwizard.h" 0035 #include "knewaccountdlg.h" 0036 #include "ktagcontainer.h" 0037 #include "tabbar.h" 0038 #include "mymoneyutils.h" 0039 #include "mymoneyexception.h" 0040 #include "kmymoneycategory.h" 0041 #include "kmymoneymvccombo.h" 0042 #include "amountedit.h" 0043 #include "kmymoneylineedit.h" 0044 #include "mymoneyfile.h" 0045 #include "mymoneyprice.h" 0046 #include "mymoneysecurity.h" 0047 #include "kmymoneyutils.h" 0048 #include "kmymoneycompletion.h" 0049 #include "transaction.h" 0050 #include "transactionform.h" 0051 #include "kmymoneysettings.h" 0052 #include "transactioneditorcontainer.h" 0053 0054 #include "kcurrencycalculator.h" 0055 #include "icons.h" 0056 0057 using namespace KMyMoneyRegister; 0058 using namespace KMyMoneyTransactionForm; 0059 using namespace Icons; 0060 0061 TransactionEditor::TransactionEditor() : 0062 d_ptr(new TransactionEditorPrivate(this)) 0063 { 0064 Q_D(TransactionEditor); 0065 d->init(); 0066 } 0067 0068 TransactionEditor::TransactionEditor(TransactionEditorPrivate &dd, 0069 TransactionEditorContainer* regForm, 0070 KMyMoneyRegister::Transaction* item, 0071 const KMyMoneyRegister::SelectedTransactions& list, 0072 const QDate& lastPostDate) : 0073 d_ptr(&dd) 0074 // d_ptr(new TransactionEditorPrivate) 0075 { 0076 Q_D(TransactionEditor); 0077 d->m_paymentMethod = eMyMoney::Schedule::PaymentType::Any; 0078 d->m_transactions = list; 0079 d->m_regForm = regForm; 0080 d->m_item = item; 0081 d->m_transaction = item->transaction(); 0082 d->m_split = item->split(); 0083 d->m_lastPostDate = lastPostDate; 0084 d->m_initialAction = eWidgets::eRegister::Action::None; 0085 d->m_openEditSplits = false; 0086 d->m_memoChanged = false; 0087 d->m_item->startEditMode(); 0088 connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, this, static_cast<void (TransactionEditor::*)()>(&TransactionEditor::slotUpdateAccount)); 0089 } 0090 0091 TransactionEditor::TransactionEditor(TransactionEditorPrivate &dd) : 0092 d_ptr(&dd) 0093 { 0094 Q_D(TransactionEditor); 0095 d->init(); 0096 } 0097 0098 TransactionEditor::~TransactionEditor() 0099 { 0100 Q_D(TransactionEditor); 0101 // Make sure the widgets do not send out signals to the editor anymore 0102 // After all, the editor is about to die 0103 0104 //disconnect first tagCombo: 0105 auto w = dynamic_cast<KTagContainer*>(haveWidget("tag")); 0106 if (w && w->tagCombo()) { 0107 w->tagCombo()->disconnect(this); 0108 } 0109 0110 QMap<QString, QWidget*>::iterator it_w; 0111 for (it_w = d->m_editWidgets.begin(); it_w != d->m_editWidgets.end(); ++it_w) { 0112 (*it_w)->disconnect(this); 0113 } 0114 0115 d->m_regForm->removeEditWidgets(d->m_editWidgets); 0116 d->m_item->leaveEditMode(); 0117 emit finishEdit(d->m_transactions); 0118 } 0119 0120 void TransactionEditor::slotUpdateAccount(const QString& id) 0121 { 0122 Q_D(TransactionEditor); 0123 d->m_account = MyMoneyFile::instance()->account(id); 0124 setupPrecision(); 0125 } 0126 0127 void TransactionEditor::slotUpdateAccount() 0128 { 0129 Q_D(TransactionEditor); 0130 // reload m_account as it might have been changed 0131 d->m_account = MyMoneyFile::instance()->account(d->m_account.id()); 0132 setupPrecision(); 0133 } 0134 0135 void TransactionEditor::setupPrecision() 0136 { 0137 Q_D(TransactionEditor); 0138 const int prec = (d->m_account.id().isEmpty()) ? 2 : MyMoneyMoney::denomToPrec(d->m_account.fraction()); 0139 QStringList widgets = QString("amount,deposit,payment").split(','); 0140 QStringList::const_iterator it_w; 0141 for (it_w = widgets.constBegin(); it_w != widgets.constEnd(); ++it_w) { 0142 QWidget * w; 0143 if ((w = haveWidget(*it_w)) != 0) { 0144 if (auto precisionWidget = dynamic_cast<AmountEdit*>(w)) 0145 precisionWidget->setPrecision(prec); 0146 } 0147 } 0148 } 0149 0150 void TransactionEditor::setup(QWidgetList& tabOrderWidgets, const MyMoneyAccount& account, eWidgets::eRegister::Action action) 0151 { 0152 Q_D(TransactionEditor); 0153 d->m_account = account; 0154 d->m_initialAction = action; 0155 createEditWidgets(); 0156 d->m_regForm->arrangeEditWidgets(d->m_editWidgets, d->m_item); 0157 d->m_regForm->tabOrder(tabOrderWidgets, d->m_item); 0158 QWidget* w = haveWidget("tabbar"); 0159 if (w) { 0160 tabOrderWidgets.append(w); 0161 auto tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(w); 0162 if ((tabbar) && (action == eWidgets::eRegister::Action::None)) { 0163 action = static_cast<eWidgets::eRegister::Action>(tabbar->currentIndex()); 0164 } 0165 } 0166 loadEditWidgets(action); 0167 0168 // remove all unused widgets and don't forget to remove them 0169 // from the tab order list as well 0170 d->m_editWidgets.removeOrphans(); 0171 QWidgetList::iterator it_w; 0172 const QWidgetList editWidgets(d->m_editWidgets.values()); 0173 for (it_w = tabOrderWidgets.begin(); it_w != tabOrderWidgets.end();) { 0174 if (editWidgets.contains(*it_w)) { 0175 ++it_w; 0176 } else { 0177 // before we remove the widget, we make sure it's not a part of a known one. 0178 // these could be a direct child in case of KMyMoneyDateInput and AmountEdit 0179 // where we store the pointer to the surrounding frame in editWidgets 0180 // or the parent is called "KMyMoneyCategoryFrame" 0181 if (*it_w) { 0182 if (editWidgets.contains((*it_w)->parentWidget()) 0183 || ((*it_w)->parentWidget() && (*it_w)->parentWidget()->objectName() == QLatin1String("KMyMoneyCategoryFrame"))) { 0184 ++it_w; 0185 0186 } else { 0187 // qDebug("Remove '%s' from taborder", qPrintable((*it_w)->objectName())); 0188 it_w = tabOrderWidgets.erase(it_w); 0189 } 0190 } else { 0191 it_w = tabOrderWidgets.erase(it_w); 0192 } 0193 } 0194 } 0195 0196 clearFinalWidgets(); 0197 setupFinalWidgets(); 0198 slotUpdateButtonState(); 0199 } 0200 0201 void TransactionEditor::setup(QWidgetList& tabOrderWidgets, const MyMoneyAccount& account) 0202 { 0203 setup(tabOrderWidgets, account, eWidgets::eRegister::Action::None); 0204 } 0205 0206 MyMoneyAccount TransactionEditor::account() const 0207 { 0208 Q_D(const TransactionEditor); 0209 return d->m_account; 0210 } 0211 0212 void TransactionEditor::setScheduleInfo(const QString& si) 0213 { 0214 Q_D(TransactionEditor); 0215 d->m_scheduleInfo = si; 0216 } 0217 0218 void TransactionEditor::setPaymentMethod(eMyMoney::Schedule::PaymentType pm) 0219 { 0220 Q_D(TransactionEditor); 0221 d->m_paymentMethod = pm; 0222 } 0223 0224 void TransactionEditor::clearFinalWidgets() 0225 { 0226 Q_D(TransactionEditor); 0227 d->m_finalEditWidgets.clear(); 0228 } 0229 0230 void TransactionEditor::addFinalWidget(const QWidget* w) 0231 { 0232 Q_D(TransactionEditor); 0233 if (w) { 0234 d->m_finalEditWidgets << w; 0235 } 0236 } 0237 0238 void TransactionEditor::slotReloadEditWidgets() 0239 { 0240 } 0241 0242 bool TransactionEditor::eventFilter(QObject* o, QEvent* e) 0243 { 0244 Q_D(TransactionEditor); 0245 bool rc = false; 0246 if (o == haveWidget("number")) { 0247 if (e->type() == QEvent::MouseButtonDblClick) { 0248 assignNextNumber(); 0249 rc = true; 0250 } 0251 } 0252 0253 // if the object is a widget, the event is a key press event and 0254 // the object is one of our edit widgets, then .... 0255 auto numberWiget = dynamic_cast<QWidget*>(o); 0256 if (o->isWidgetType() 0257 && (e->type() == QEvent::KeyPress) 0258 && numberWiget && d->m_editWidgets.values().contains(numberWiget)) { 0259 auto k = dynamic_cast<QKeyEvent*>(e); 0260 if (k && (((k->modifiers() & Qt::KeyboardModifierMask) == 0) 0261 || ((k->modifiers() & Qt::KeypadModifier) != 0))) { 0262 bool isFinal = false; 0263 QList<const QWidget*>::const_iterator it_w; 0264 switch (k->key()) { 0265 case Qt::Key_Return: 0266 case Qt::Key_Enter: 0267 // we check, if the object is one of the m_finalEditWidgets and if it's 0268 // a AmountEdit object that the value is not 0. If any of that is the 0269 // case, it's the final object. In other cases, we convert the enter 0270 // key into a TAB key to move between the fields. Of course, we only need 0271 // to do this as long as the appropriate option is set. In all other cases, 0272 // we treat the return/enter key as such. 0273 if (KMyMoneySettings::enterMovesBetweenFields()) { 0274 for (it_w = d->m_finalEditWidgets.constBegin(); !isFinal && it_w != d->m_finalEditWidgets.constEnd(); ++it_w) { 0275 if (*it_w == o) { 0276 if (auto widget = dynamic_cast<const AmountEdit*>(*it_w)) { 0277 isFinal = !(widget->value().isZero()); 0278 } else 0279 isFinal = true; 0280 } 0281 } 0282 } else 0283 isFinal = true; 0284 0285 // for the non-final objects, we treat the return key as a TAB 0286 if (!isFinal) { 0287 QKeyEvent evt(e->type(), 0288 Qt::Key_Tab, k->modifiers(), QString(), 0289 k->isAutoRepeat(), k->count()); 0290 0291 QApplication::sendEvent(o, &evt); 0292 // in case of a category item and the split button is visible 0293 // send a second event so that we get passed the button. 0294 auto widget = dynamic_cast<KMyMoneyCategory*>(o); 0295 if (widget && widget->splitButton()) 0296 QApplication::sendEvent(o, &evt); 0297 0298 } else if (!d->m_readOnly) { 0299 QTimer::singleShot(0, this, SIGNAL(returnPressed())); 0300 } 0301 // don't process any further 0302 rc = true; 0303 break; 0304 0305 case Qt::Key_Escape: 0306 QTimer::singleShot(0, this, SIGNAL(escapePressed())); 0307 break; 0308 } 0309 } 0310 } 0311 return rc; 0312 } 0313 0314 void TransactionEditor::slotUpdateMemoState() 0315 { 0316 Q_D(TransactionEditor); 0317 KTextEdit* memo = dynamic_cast<KTextEdit*>(d->m_editWidgets["memo"]); 0318 if (memo) { 0319 d->m_memoChanged = (memo->toPlainText() != d->m_memoText); 0320 } 0321 } 0322 0323 void TransactionEditor::slotUpdateButtonState() 0324 { 0325 QString reason; 0326 emit transactionDataSufficient(isComplete(reason)); 0327 } 0328 0329 QWidget* TransactionEditor::haveWidget(const QString& name) const 0330 { 0331 Q_D(const TransactionEditor); 0332 return d->m_editWidgets.haveWidget(name); 0333 } 0334 0335 int TransactionEditor::slotEditSplits() 0336 { 0337 return QDialog::Rejected; 0338 } 0339 0340 void TransactionEditor::setTransaction(const MyMoneyTransaction& t, const MyMoneySplit& s) 0341 { 0342 Q_D(TransactionEditor); 0343 d->m_transaction = t; 0344 d->m_split = s; 0345 loadEditWidgets(); 0346 } 0347 0348 bool TransactionEditor::isMultiSelection() const 0349 { 0350 Q_D(const TransactionEditor); 0351 return d->m_transactions.count() > 1; 0352 } 0353 0354 bool TransactionEditor::fixTransactionCommodity(const MyMoneyAccount& account) 0355 { 0356 Q_D(TransactionEditor); 0357 bool rc = true; 0358 bool firstTimeMultiCurrency = true; 0359 d->m_account = account; 0360 0361 auto file = MyMoneyFile::instance(); 0362 0363 // determine the max fraction for this account 0364 MyMoneySecurity sec = file->security(d->m_account.currencyId()); 0365 int fract = d->m_account.fraction(); 0366 0367 // scan the list of selected transactions 0368 KMyMoneyRegister::SelectedTransactions::iterator it_t; 0369 for (it_t = d->m_transactions.begin(); (rc == true) && (it_t != d->m_transactions.end()); ++it_t) { 0370 // there was a time when the schedule editor did not setup the transaction commodity 0371 // let's give a helping hand here for those old schedules 0372 if ((*it_t).transaction().commodity().isEmpty()) 0373 (*it_t).transaction().setCommodity(d->m_account.currencyId()); 0374 // we need to check things only if a different commodity is used 0375 if (d->m_account.currencyId() != (*it_t).transaction().commodity()) { 0376 MyMoneySecurity osec = file->security((*it_t).transaction().commodity()); 0377 switch ((*it_t).transaction().splitCount()) { 0378 case 0: 0379 // new transaction, guess nothing's here yet ;) 0380 break; 0381 0382 case 1: 0383 try { 0384 // make sure, that the value is equal to the shares, don't forget our own copy 0385 MyMoneySplit& splitB = (*it_t).split(); // reference usage wanted here 0386 if (d->m_split == splitB) 0387 d->m_split.setValue(splitB.shares()); 0388 splitB.setValue(splitB.shares()); 0389 (*it_t).transaction().modifySplit(splitB); 0390 0391 } catch (const MyMoneyException &e) { 0392 qDebug("Unable to update commodity to second splits currency in %s: '%s'", qPrintable((*it_t).transaction().id()), e.what()); 0393 } 0394 break; 0395 0396 case 2: 0397 // If we deal with multiple currencies we make sure, that for 0398 // transactions with two splits, the transaction's commodity is the 0399 // currency of the currently selected account. This saves us from a 0400 // lot of grieve later on. We just have to switch the 0401 // transactions commodity. Let's assume the following scenario: 0402 // - transactions commodity is CA 0403 // - splitB and account's currencyId is CB 0404 // - splitA is of course in CA (otherwise we have a real problem) 0405 // - Value is V in both splits 0406 // - Shares in splitB is SB 0407 // - Shares in splitA is SA (and equal to V) 0408 // 0409 // We do the following: 0410 // - change transactions commodity to CB 0411 // - set V in both splits to SB 0412 // - modify the splits in the transaction 0413 try { 0414 // retrieve the splits 0415 MyMoneySplit& splitB = (*it_t).split(); // reference usage wanted here 0416 MyMoneySplit splitA = (*it_t).transaction().splitByAccount(d->m_account.id(), false); 0417 0418 // - set V in both splits to SB. Don't forget our own copy 0419 if (d->m_split == splitB) { 0420 d->m_split.setValue(splitB.shares()); 0421 } 0422 splitB.setValue(splitB.shares()); 0423 splitA.setValue(-splitB.shares()); 0424 (*it_t).transaction().modifySplit(splitA); 0425 (*it_t).transaction().modifySplit(splitB); 0426 0427 } catch (const MyMoneyException &e) { 0428 qDebug("Unable to update commodity to second splits currency in %s: '%s'", qPrintable((*it_t).transaction().id()), e.what()); 0429 } 0430 break; 0431 0432 default: 0433 // TODO: use new logic by adjusting all splits by the price 0434 // extracted from the selected split. Inform the user that 0435 // this will happen and allow him to stop the processing (rc = false) 0436 0437 try { 0438 QString msg; 0439 if (firstTimeMultiCurrency) { 0440 firstTimeMultiCurrency = false; 0441 if (!isMultiSelection()) { 0442 msg = i18n("This transaction has more than two splits and is originally based on a different currency (%1). Using this account to modify the transaction may result in rounding errors. Do you want to continue?", osec.name()); 0443 } else { 0444 msg = i18n("At least one of the selected transactions has more than two splits and is originally based on a different currency (%1). Using this account to modify the transactions may result in rounding errors. Do you want to continue?", osec.name()); 0445 } 0446 0447 if (KMessageBox::warningContinueCancel(0, QString("<qt>%1</qt>").arg(msg)) == KMessageBox::Cancel) { 0448 rc = false; 0449 } 0450 } 0451 0452 if (rc == true) { 0453 MyMoneyMoney price; 0454 if (!(*it_t).split().shares().isZero() && !(*it_t).split().value().isZero()) 0455 price = (*it_t).split().shares() / (*it_t).split().value(); 0456 MyMoneySplit& mySplit = (*it_t).split(); 0457 foreach (const auto split, (*it_t).transaction().splits()) { 0458 auto s = split; 0459 if (s == mySplit) { 0460 s.setValue(s.shares()); 0461 if (mySplit == d->m_split) { 0462 d->m_split = s; 0463 } 0464 mySplit = s; 0465 } else { 0466 s.setValue((s.value() * price).convert(fract)); 0467 } 0468 (*it_t).transaction().modifySplit(s); 0469 } 0470 } 0471 } catch (const MyMoneyException &e) { 0472 qDebug("Unable to update commodity of split currency in %s: '%s'", qPrintable((*it_t).transaction().id()), e.what()); 0473 } 0474 break; 0475 } 0476 0477 // set the transaction's ommodity to this account's currency 0478 (*it_t).transaction().setCommodity(d->m_account.currencyId()); 0479 0480 // update our copy of the transaction that has the focus 0481 if ((*it_t).transaction().id() == d->m_transaction.id()) { 0482 d->m_transaction = (*it_t).transaction(); 0483 } 0484 } 0485 } 0486 return rc; 0487 } 0488 0489 QString TransactionEditor::validateCheckNumber(const QString& num) const 0490 { 0491 Q_D(const TransactionEditor); 0492 0493 int rc = KMessageBox::No; 0494 QString schedInfo; 0495 if (!d->m_scheduleInfo.isEmpty()) { 0496 schedInfo = i18n("<center>Processing schedule for %1.</center>", d->m_scheduleInfo); 0497 } 0498 if (MyMoneyFile::instance()->checkNoUsed(d->m_account.id(), num)) { 0499 rc = KMessageBox::questionYesNo(d->m_regForm, QString("<qt>") + schedInfo + i18n("Check number <b>%1</b> has already been used in account <b>%2</b>." 0500 "<center>Do you want to replace it with the next available number?</center>", num, d->m_account.name()) + QString("</qt>"), i18n("Duplicate number")); 0501 if (rc == KMessageBox::Yes) { 0502 return KMyMoneyUtils::nextFreeCheckNumber(d->m_account); 0503 } 0504 } 0505 return num; 0506 } 0507 0508 void TransactionEditor::assignNextNumber() 0509 { 0510 Q_D(TransactionEditor); 0511 auto number = dynamic_cast<KMyMoneyLineEdit*>(haveWidget("number")); 0512 if (number) { 0513 const auto num = validateCheckNumber(KMyMoneyUtils::nextCheckNumber(d->m_account)); 0514 d->m_account.setValue("lastNumberUsed", num); 0515 number->setText(num); 0516 } 0517 } 0518 0519 void TransactionEditor::slotNumberChanged(const QString& txt) 0520 { 0521 auto number = dynamic_cast<KMyMoneyLineEdit*>(haveWidget("number")); 0522 if (number) { 0523 const auto next = validateCheckNumber(txt); 0524 if (next != txt) { 0525 number->setText(next); 0526 } 0527 } 0528 } 0529 0530 0531 bool TransactionEditor::canAssignNumber() const 0532 { 0533 if (dynamic_cast<KMyMoneyLineEdit*>(haveWidget("number"))) 0534 return true; 0535 return false; 0536 } 0537 0538 void TransactionEditor::setupCategoryWidget(KMyMoneyCategory* category, const QList<MyMoneySplit>& splits, QString& categoryId, const char* splitEditSlot, bool /* allowObjectCreation */) 0539 { 0540 disconnect(category, SIGNAL(focusIn()), this, splitEditSlot); 0541 #if 0 0542 // FIXME must deal with the logic that suppressObjectCreation is 0543 // automatically turned off when the createItem() signal is connected 0544 if (allowObjectCreation) 0545 category->setSuppressObjectCreation(false); 0546 #endif 0547 0548 switch (splits.count()) { 0549 case 0: 0550 categoryId.clear(); 0551 if (!category->currentText().isEmpty()) { 0552 // category->clearEditText(); // don't clear as could be from another widget - Bug 322768 0553 // make sure, we don't see the selector 0554 category->completion()->hide(); 0555 } 0556 category->completion()->setSelected(QString()); 0557 break; 0558 0559 case 1: 0560 categoryId = splits[0].accountId(); 0561 category->completion()->setSelected(categoryId); 0562 category->slotItemSelected(categoryId); 0563 break; 0564 0565 default: 0566 categoryId.clear(); 0567 category->setSplitTransaction(); 0568 connect(category, SIGNAL(focusIn()), this, splitEditSlot); 0569 #if 0 0570 // FIXME must deal with the logic that suppressObjectCreation is 0571 // automatically turned off when the createItem() signal is connected 0572 if (allowObjectCreation) 0573 category->setSuppressObjectCreation(true); 0574 #endif 0575 break; 0576 } 0577 } 0578 0579 bool TransactionEditor::createNewTransaction() const 0580 { 0581 Q_D(const TransactionEditor); 0582 0583 bool rc = true; 0584 if (!d->m_transactions.isEmpty()) { 0585 rc = d->m_transactions.at(0).transaction().id().isEmpty(); 0586 } 0587 return rc; 0588 } 0589 0590 bool TransactionEditor::enterTransactions(QString& newId, bool askForSchedule, bool suppressBalanceWarnings) 0591 { 0592 Q_D(TransactionEditor); 0593 newId.clear(); 0594 auto file = MyMoneyFile::instance(); 0595 0596 // make sure to run through all stuff that is tied to 'focusout events'. 0597 d->m_regForm->parentWidget()->setFocus(); 0598 QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 10); 0599 // we don't need to update our widgets anymore, so we just disconnect the signal 0600 disconnect(file, &MyMoneyFile::dataChanged, this, &TransactionEditor::slotReloadEditWidgets); 0601 0602 KMyMoneyRegister::SelectedTransactions::iterator it_t; 0603 MyMoneyTransaction t; 0604 bool newTransactionCreated = false; 0605 0606 // make sure, that only a single new transaction can be created. 0607 // we need to update m_transactions to contain the new transaction 0608 // which is then stored in the variable t when we leave the loop. 0609 // m_transactions will be sent out in finishEdit() and forces 0610 // the new transaction to be selected in the ledger view 0611 0612 // collect the transactions to be stored in the engine in a local 0613 // list first, so that the user has a chance to interrupt the storage 0614 // process 0615 QList<MyMoneyTransaction> list; 0616 auto storeTransactions = true; 0617 0618 // collect transactions 0619 for (it_t = d->m_transactions.begin(); storeTransactions && !newTransactionCreated && it_t != d->m_transactions.end(); ++it_t) { 0620 storeTransactions = createTransaction(t, (*it_t).transaction(), (*it_t).split()); 0621 // if the transaction was created successfully, append it to the list 0622 if (storeTransactions) 0623 list.append(t); 0624 0625 // if we created a new transaction keep that in mind 0626 if (t.id().isEmpty()) 0627 newTransactionCreated = true; 0628 } 0629 0630 // if not interrupted by user, continue to store them in the engine 0631 if (storeTransactions) { 0632 auto i = 0; 0633 emit statusMsg(i18n("Storing transactions")); 0634 emit statusProgress(0, list.count()); 0635 0636 MyMoneyFileTransaction ft; 0637 0638 try { 0639 QMap<QString, bool> minBalanceEarly; 0640 QMap<QString, bool> minBalanceAbsolute; 0641 QMap<QString, bool> maxCreditEarly; 0642 QMap<QString, bool> maxCreditAbsolute; 0643 QMap<QString, bool> accountIds; 0644 0645 for (MyMoneyTransaction& transaction : list) { 0646 // if we have a categorization, make sure we remove 0647 // the 'imported' flag automagically 0648 if (transaction.splitCount() > 1) 0649 transaction.setImported(false); 0650 0651 // create information about min and max balances 0652 foreach (const auto split, transaction.splits()) { 0653 auto acc = file->account(split.accountId()); 0654 accountIds[acc.id()] = true; 0655 MyMoneyMoney balance = file->balance(acc.id()); 0656 if (!acc.value("minBalanceEarly").isEmpty()) { 0657 minBalanceEarly[acc.id()] = balance < MyMoneyMoney(acc.value("minBalanceEarly")); 0658 } 0659 if (!acc.value("minBalanceAbsolute").isEmpty()) { 0660 minBalanceAbsolute[acc.id()] = balance < MyMoneyMoney(acc.value("minBalanceAbsolute")); 0661 minBalanceEarly[acc.id()] = false; 0662 } 0663 if (!acc.value("maxCreditEarly").isEmpty()) { 0664 maxCreditEarly[acc.id()] = balance < MyMoneyMoney(acc.value("maxCreditEarly")); 0665 } 0666 if (!acc.value("maxCreditAbsolute").isEmpty()) { 0667 maxCreditAbsolute[acc.id()] = balance < MyMoneyMoney(acc.value("maxCreditAbsolute")); 0668 maxCreditEarly[acc.id()] = false; 0669 } 0670 0671 // and adjust opening date of invest accounts 0672 if (acc.isInvest()) { 0673 if (acc.openingDate() > t.postDate()) { 0674 try { 0675 acc.setOpeningDate(t.postDate()); 0676 file->modifyAccount(acc); 0677 } catch(MyMoneyException& ) { 0678 qDebug() << "Unable to modify opening date for invest account" << acc.name() << acc.id(); 0679 } 0680 } 0681 } 0682 } 0683 0684 if (transaction.id().isEmpty()) { 0685 bool enter = true; 0686 if (askForSchedule && transaction.postDate() > QDate::currentDate()) { 0687 KGuiItem enterButton(i18n("&Enter"), 0688 Icons::get(Icon::DialogOK), 0689 i18n("Accepts the entered data and stores it"), 0690 i18n("Use this to enter the transaction into the ledger.")); 0691 KGuiItem scheduleButton(i18n("&Schedule"), 0692 Icons::get(Icon::NewSchedule), 0693 i18n("Accepts the entered data and stores it as schedule"), 0694 i18n("Use this to schedule the transaction for later entry into the ledger.")); 0695 0696 enter = KMessageBox::questionYesNo(d->m_regForm, QString("<qt>%1</qt>").arg(i18n("The transaction you are about to enter has a post date in the future.<br/><br/>Do you want to enter it in the ledger or add it to the schedules?")), i18nc("Dialog caption for 'Enter or schedule' dialog", "Enter or schedule?"), enterButton, scheduleButton, "EnterOrScheduleTransactionInFuture") == KMessageBox::Yes; 0697 } 0698 if (enter) { 0699 // add new transaction 0700 file->addTransaction(transaction); 0701 // pass the newly assigned id on to the caller 0702 newId = transaction.id(); 0703 // refresh account object for transactional changes 0704 // refresh account and transaction object because they might have changed 0705 d->m_account = file->account(d->m_account.id()); 0706 t = transaction; 0707 0708 // if a new transaction has a valid number, keep it with the account 0709 d->keepNewNumber(transaction); 0710 } else { 0711 // turn object creation on, so that moving the focus does 0712 // not screw up the dialog that might be popping up 0713 emit objectCreation(true); 0714 emit scheduleTransaction(transaction, eMyMoney::Schedule::Occurrence::Once); 0715 emit objectCreation(false); 0716 0717 newTransactionCreated = false; 0718 } 0719 0720 // send out the post date of this transaction 0721 emit lastPostDateUsed(transaction.postDate()); 0722 } else { 0723 // modify existing transaction 0724 // its number might have been edited 0725 // bearing in mind it could contain alpha characters 0726 d->keepNewNumber(transaction); 0727 file->modifyTransaction(transaction); 0728 } 0729 } 0730 emit statusProgress(i++, 0); 0731 0732 // update m_transactions to contain the newly created transaction so that 0733 // it is selected as the current one 0734 // we need to do that before we commit the transaction to the engine 0735 // as we need it during the update of the views that is caused by committing already. 0736 if (newTransactionCreated) { 0737 d->m_transactions.clear(); 0738 MyMoneySplit s; 0739 // a transaction w/o a single split should not exist and adding it 0740 // should throw an exception in MyMoneyFile::addTransaction, but we 0741 // remain on the save side of things to check for it 0742 if (t.splitCount() > 0) 0743 s = t.splits().front(); 0744 KMyMoneyRegister::SelectedTransaction st(t, s, QString()); 0745 d->m_transactions.append(st); 0746 } 0747 0748 // Save pricing information 0749 foreach (const auto split, t.splits()) { 0750 if ((split.action() != "Buy") && 0751 (split.action() != "Reinvest")) { 0752 continue; 0753 } 0754 QString id = split.accountId(); 0755 auto acc = file->account(id); 0756 MyMoneySecurity sec = file->security(acc.currencyId()); 0757 MyMoneyPrice price(acc.currencyId(), 0758 sec.tradingCurrency(), 0759 t.postDate(), 0760 split.price(), "Transaction"); 0761 file->addPrice(price); 0762 break; 0763 } 0764 0765 ft.commit(); 0766 0767 // now analyze the balances and spit out warnings to the user 0768 QMap<QString, bool>::const_iterator it_a; 0769 0770 if (!suppressBalanceWarnings) { 0771 for (it_a = accountIds.constBegin(); it_a != accountIds.constEnd(); ++it_a) { 0772 QString msg; 0773 auto acc = file->account(it_a.key()); 0774 MyMoneyMoney balance = file->balance(acc.id()); 0775 const MyMoneySecurity& sec = file->security(acc.currencyId()); 0776 QString key; 0777 key = "minBalanceEarly"; 0778 if (!acc.value(key).isEmpty()) { 0779 if (minBalanceEarly[acc.id()] == false && balance < MyMoneyMoney(acc.value(key))) { 0780 msg = QString("<qt>%1</qt>").arg(i18n("The balance of account <b>%1</b> dropped below the warning balance of %2.", acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(acc.value(key)), acc, sec))); 0781 } 0782 } 0783 key = "minBalanceAbsolute"; 0784 if (!acc.value(key).isEmpty()) { 0785 if (minBalanceAbsolute[acc.id()] == false && balance < MyMoneyMoney(acc.value(key))) { 0786 msg = QString("<qt>%1</qt>").arg(i18n("The balance of account <b>%1</b> dropped below the minimum balance of %2.", acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(acc.value(key)), acc, sec))); 0787 } 0788 } 0789 key = "maxCreditEarly"; 0790 if (!acc.value(key).isEmpty()) { 0791 if (maxCreditEarly[acc.id()] == false && balance < MyMoneyMoney(acc.value(key))) { 0792 msg = QString("<qt>%1</qt>").arg(i18n("The balance of account <b>%1</b> dropped below the maximum credit warning limit of %2.", acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(acc.value(key)), acc, sec))); 0793 } 0794 } 0795 key = "maxCreditAbsolute"; 0796 if (!acc.value(key).isEmpty()) { 0797 if (maxCreditAbsolute[acc.id()] == false && balance < MyMoneyMoney(acc.value(key))) { 0798 msg = QString("<qt>%1</qt>").arg(i18n("The balance of account <b>%1</b> dropped below the maximum credit limit of %2.", acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(acc.value(key)), acc, sec))); 0799 } 0800 } 0801 0802 if (!msg.isEmpty()) { 0803 emit balanceWarning(d->m_regForm, acc, msg); 0804 } 0805 } 0806 } 0807 } catch (const MyMoneyException &e) { 0808 qDebug("Unable to store transaction within engine: %s", e.what()); 0809 } 0810 0811 emit statusProgress(-1, -1); 0812 emit statusMsg(QString()); 0813 0814 } 0815 return storeTransactions; 0816 } 0817 0818 void TransactionEditor::resizeForm() 0819 { 0820 Q_D(TransactionEditor); 0821 // force resizeing of the columns in the form 0822 0823 if (auto form = dynamic_cast<KMyMoneyTransactionForm::TransactionForm*>(d->m_regForm)) 0824 QMetaObject::invokeMethod(form, "resize", Qt::QueuedConnection, QGenericReturnArgument(), Q_ARG(int, (int)eWidgets::eTransactionForm::Column::Value1)); 0825 } 0826 0827 void TransactionEditor::slotNewPayee(const QString& newnameBase, QString& id) 0828 { 0829 KMyMoneyUtils::newPayee(newnameBase, id); 0830 } 0831 0832 void TransactionEditor::slotNewTag(const QString& newnameBase, QString& id) 0833 { 0834 KMyMoneyUtils::newTag(newnameBase, id); 0835 } 0836 0837 void TransactionEditor::slotNewCategory(MyMoneyAccount& account, const MyMoneyAccount& parent) 0838 { 0839 KNewAccountDlg::newCategory(account, parent); 0840 } 0841 0842 void TransactionEditor::slotNewInvestment(MyMoneyAccount& account, const MyMoneyAccount& parent) 0843 { 0844 KNewInvestmentWizard::newInvestment(account, parent); 0845 } 0846 0847 void TransactionEditor::setReadOnlyMode(bool readOnly) 0848 { 0849 Q_D(TransactionEditor); 0850 d->m_readOnly = readOnly; 0851 }