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 }