File indexing completed on 2024-05-12 05:07:47

0001 /*
0002     SPDX-FileCopyrightText: 2023 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "multitransactioneditor.h"
0007 
0008 // ----------------------------------------------------------------------------
0009 // QT Includes
0010 
0011 #include <QAbstractItemView>
0012 #include <QCompleter>
0013 #include <QDebug>
0014 #include <QGlobalStatic>
0015 #include <QHeaderView>
0016 #include <QSortFilterProxyModel>
0017 #include <QStandardItemModel>
0018 #include <QStringList>
0019 #include <QTableView>
0020 #include <QTimer>
0021 
0022 // ----------------------------------------------------------------------------
0023 // KDE Includes
0024 
0025 #include <KLocalizedString>
0026 
0027 // ----------------------------------------------------------------------------
0028 // Project Includes
0029 
0030 #include "accountcreator.h"
0031 #include "costcentermodel.h"
0032 #include "icons.h"
0033 #include "idfilter.h"
0034 #include "journalmodel.h"
0035 #include "kcurrencycalculator.h"
0036 #include "kmymoneysettings.h"
0037 #include "knewaccountdlg.h"
0038 #include "mymoneyaccount.h"
0039 #include "mymoneyexception.h"
0040 #include "mymoneyfile.h"
0041 #include "mymoneypayee.h"
0042 #include "mymoneyschedule.h"
0043 #include "mymoneysecurity.h"
0044 #include "mymoneysplit.h"
0045 #include "mymoneytransaction.h"
0046 #include "mymoneyutils.h"
0047 #include "payeesmodel.h"
0048 #include "securitiesmodel.h"
0049 #include "splitdialog.h"
0050 #include "statusmodel.h"
0051 #include "tagsmodel.h"
0052 #include "widgethintframe.h"
0053 
0054 #include "ui_newtransactioneditor.h"
0055 
0056 using namespace Icons;
0057 
0058 class MultiTransactionEditor::Private
0059 {
0060     Q_DISABLE_COPY_MOVE(Private)
0061 
0062 public:
0063     enum TaxValueChange {
0064         ValueUnchanged,
0065         ValueChanged,
0066     };
0067     Private(MultiTransactionEditor* parent)
0068         : q(parent)
0069         , ui(new Ui_NewTransactionEditor)
0070         , tabOrderUi(nullptr)
0071         , accountsModel(new AccountNamesFilterProxyModel(parent))
0072         , categoriesModel(new AccountNamesFilterProxyModel(parent))
0073         , costCenterModel(new QSortFilterProxyModel(parent))
0074         , payeesModel(new QSortFilterProxyModel(parent))
0075         , frameCollection(nullptr)
0076         , costCenterRequired(false)
0077     {
0078         accountsModel->setObjectName(QLatin1String("MultiTransactionEditor::accountsModel"));
0079         categoriesModel->setObjectName(QLatin1String("MultiTransactionEditor::categoriesModel"));
0080         costCenterModel->setObjectName(QLatin1String("SortedCostCenterModel"));
0081         payeesModel->setObjectName(QLatin1String("SortedPayeesModel"));
0082 
0083         costCenterModel->setSortLocaleAware(true);
0084         costCenterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
0085 
0086         payeesModel->setSortLocaleAware(true);
0087         payeesModel->setSortCaseSensitivity(Qt::CaseInsensitive);
0088     }
0089 
0090     ~Private()
0091     {
0092         delete ui;
0093     }
0094 
0095     void setupTabOrder();
0096     bool anyChanges() const;
0097     bool isDatePostOpeningDate(const QDate& date, const QString& accountId);
0098     bool checkForValidTransaction(bool doUserInteraction);
0099     bool postdateChanged(const QDate& date);
0100     bool costCenterChanged(int costCenterIndex);
0101     void payeeChanged(int payeeIndex);
0102     void accountChanged(const QString& id);
0103     bool categoryChanged(const QString& id);
0104     void numberChanged(const QString& newNumber);
0105     void amountChanged();
0106     bool isIncomeExpense(const QModelIndex& idx) const;
0107     bool isIncomeExpense(const QString& categoryId) const;
0108     void tagsChanged(const QStringList& tagIds);
0109 
0110     MultiTransactionEditor* q;
0111     Ui_NewTransactionEditor* ui;
0112     Ui_NewTransactionEditor* tabOrderUi;
0113     AccountNamesFilterProxyModel* accountsModel;
0114     AccountNamesFilterProxyModel* categoriesModel;
0115     QSortFilterProxyModel* costCenterModel;
0116     QSortFilterProxyModel* payeesModel;
0117     QUndoStack undoStack;
0118     WidgetHintFrameCollection* frameCollection;
0119     QString errorMessage;
0120     StatusModel statusModel;
0121     QStringList selectedJournalEntryIds;
0122     bool costCenterRequired;
0123 };
0124 
0125 bool MultiTransactionEditor::Private::anyChanges() const
0126 {
0127     bool rc = false;
0128     rc |= !ui->dateEdit->isNull();
0129     rc |= ui->creditDebitEdit->haveValue();
0130     rc |= !ui->payeeEdit->lineEdit()->text().isEmpty();
0131     rc |= !ui->categoryCombo->getSelected().isEmpty();
0132     rc |= !ui->costCenterCombo->lineEdit()->text().isEmpty();
0133     rc |= !ui->tagContainer->selectedTags().isEmpty();
0134     rc |= !ui->statusCombo->currentText().isEmpty();
0135     rc |= !ui->memoEdit->toPlainText().isEmpty();
0136     return rc;
0137 }
0138 
0139 bool MultiTransactionEditor::Private::checkForValidTransaction(bool doUserInteraction)
0140 {
0141     QStringList infos;
0142     bool rc = true;
0143     if (!postdateChanged(ui->dateEdit->date())) {
0144         infos << ui->dateEdit->toolTip();
0145         rc = false;
0146     }
0147 
0148     if (!costCenterChanged(ui->costCenterCombo->currentIndex())) {
0149         infos << ui->costCenterCombo->toolTip();
0150         rc = false;
0151     }
0152 
0153     if (q->needCreateCategory(ui->categoryCombo) || q->needCreatePayee(ui->payeeEdit)) {
0154         rc = false;
0155     }
0156 
0157     if (doUserInteraction) {
0158         /// @todo add dialog here that shows the @a infos about the problem
0159     }
0160     return rc;
0161 }
0162 
0163 bool MultiTransactionEditor::Private::isDatePostOpeningDate(const QDate& date, const QString& accountId)
0164 {
0165     bool rc = true;
0166 
0167     try {
0168         MyMoneyAccount account = MyMoneyFile::instance()->account(accountId);
0169         const bool isIncomeExpense = account.isIncomeExpense();
0170 
0171         // we don't check for categories
0172         if (!isIncomeExpense) {
0173             if (date < account.openingDate())
0174                 rc = false;
0175         }
0176     } catch (MyMoneyException&) {
0177         qDebug() << "Ooops: invalid account id" << accountId << "in" << Q_FUNC_INFO;
0178     }
0179     return rc;
0180 }
0181 
0182 bool MultiTransactionEditor::Private::postdateChanged(const QDate& date)
0183 {
0184     WidgetHintFrame::hide(ui->dateEdit, i18n("The posting date of the transaction."));
0185 
0186     // if the date field is empty, we have a valid date
0187     if (ui->dateEdit->isNull()) {
0188         return true;
0189     }
0190 
0191     if (!date.isValid()) {
0192         WidgetHintFrame::show(ui->dateEdit, i18n("The posting date is invalid."));
0193         return false;
0194     }
0195 
0196     // collect all account ids of all selected transactions
0197     QStringList accountIds;
0198     if (!ui->categoryCombo->getSelected().isEmpty()) {
0199         accountIds << ui->categoryCombo->getSelected();
0200     }
0201     const auto journalModel = MyMoneyFile::instance()->journalModel();
0202     for (const auto& journalEntryId : selectedJournalEntryIds) {
0203         const auto journalEntry = journalModel->itemById(journalEntryId);
0204         const auto splits = journalEntry.transaction().splits();
0205         for (const auto& sp : splits) {
0206             accountIds << sp.accountId();
0207         }
0208     }
0209 
0210     bool rc = true;
0211     for (const auto& accountId : accountIds) {
0212         if (!isDatePostOpeningDate(date, accountId)) {
0213             MyMoneyAccount account = MyMoneyFile::instance()->account(accountId);
0214             WidgetHintFrame::show(ui->dateEdit, i18n("The posting date is prior to the opening date of account <b>%1</b>.", account.name()));
0215             rc = false;
0216             break;
0217         }
0218     }
0219     return rc;
0220 }
0221 
0222 bool MultiTransactionEditor::Private::costCenterChanged(int costCenterIndex)
0223 {
0224     bool rc = true;
0225     WidgetHintFrame::hide(ui->costCenterCombo, i18n("The cost center this transaction should be assigned to."));
0226     if (costCenterIndex != -1) {
0227         if (costCenterRequired && ui->costCenterCombo->currentText().isEmpty()) {
0228             WidgetHintFrame::show(ui->costCenterCombo, i18n("A cost center assignment is required for a transaction in the selected category."));
0229             rc = false;
0230         }
0231     }
0232 
0233     return rc;
0234 }
0235 
0236 bool MultiTransactionEditor::Private::isIncomeExpense(const QString& categoryId) const
0237 {
0238     if (!categoryId.isEmpty()) {
0239         MyMoneyAccount category = MyMoneyFile::instance()->account(categoryId);
0240         return category.isIncomeExpense();
0241     }
0242     return false;
0243 }
0244 
0245 bool MultiTransactionEditor::Private::categoryChanged(const QString& accountId)
0246 {
0247     WidgetHintFrame::hide(ui->categoryCombo, i18n("Select category or account."));
0248     bool rc = true;
0249     if (!accountId.isEmpty()) {
0250         try {
0251             MyMoneyAccount category = MyMoneyFile::instance()->account(accountId);
0252             ui->costCenterCombo->setEnabled(false);
0253             ui->costCenterLabel->setEnabled(false);
0254             if (category.isAssetLiability()) {
0255                 const auto journalModel = MyMoneyFile::instance()->journalModel();
0256                 for (const auto& journalEntryId : selectedJournalEntryIds) {
0257                     const auto journalEntry = journalModel->itemById(journalEntryId);
0258                     const auto postDate = journalEntry.transaction().postDate();
0259                     if (postDate < category.openingDate()) {
0260                         QString msg = i18nc("@info Error when selecting account",
0261                                             "A selected transaction has a post date (%1) before the opening date of the selected account (%2).",
0262                                             MyMoneyUtils::formatDate(postDate),
0263                                             MyMoneyUtils::formatDate(category.openingDate()));
0264                         WidgetHintFrame::show(ui->categoryCombo, msg);
0265                         rc = false;
0266                     }
0267                 }
0268             } else if (category.isIncomeExpense()) {
0269                 ui->costCenterCombo->setEnabled(true);
0270                 ui->costCenterLabel->setEnabled(true);
0271                 costCenterRequired = category.isCostCenterRequired();
0272                 costCenterChanged(ui->costCenterCombo->currentIndex());
0273             }
0274         } catch (MyMoneyException&) {
0275             qDebug() << "Ooops: invalid account id" << accountId << "in" << Q_FUNC_INFO;
0276         }
0277     }
0278     return rc;
0279 }
0280 
0281 void MultiTransactionEditor::Private::numberChanged(const QString& newNumber)
0282 {
0283     Q_UNUSED(newNumber)
0284 }
0285 
0286 void MultiTransactionEditor::Private::amountChanged()
0287 {
0288 }
0289 
0290 void MultiTransactionEditor::Private::payeeChanged(int payeeIndex)
0291 {
0292     Q_UNUSED(payeeIndex)
0293 }
0294 
0295 void MultiTransactionEditor::Private::tagsChanged(const QStringList& tagIds)
0296 {
0297     Q_UNUSED(tagIds)
0298 }
0299 
0300 void MultiTransactionEditor::Private::setupTabOrder()
0301 {
0302     const auto defaultTabOrder = QStringList{
0303         QLatin1String("accountCombo"),
0304         QLatin1String("dateEdit"),
0305         QLatin1String("creditDebitEdit"),
0306         QLatin1String("payeeEdit"),
0307         QLatin1String("numberEdit"),
0308         QLatin1String("categoryCombo"),
0309         QLatin1String("costCenterCombo"),
0310         QLatin1String("tagContainer"),
0311         QLatin1String("statusCombo"),
0312         QLatin1String("memoEdit"),
0313         QLatin1String("enterButton"),
0314         QLatin1String("cancelButton"),
0315     };
0316     q->setProperty("kmm_defaulttaborder", defaultTabOrder);
0317     q->setProperty("kmm_currenttaborder", q->tabOrder(QLatin1String("multiTransactionEditor"), defaultTabOrder));
0318 
0319     q->setupTabOrder(q->property("kmm_currenttaborder").toStringList());
0320 }
0321 
0322 MultiTransactionEditor::MultiTransactionEditor(QWidget* parent, const QString& accountId)
0323     : TransactionEditorBase(parent, accountId)
0324     , d(new Private(this))
0325 {
0326     auto const file = MyMoneyFile::instance();
0327     auto const model = file->accountsModel();
0328 
0329     d->ui->setupUi(this);
0330 
0331     // default is to hide the account selection combobox
0332     // and the number widget
0333     setShowAccountCombo(false);
0334     setShowNumberWidget(false);
0335 
0336     d->setupTabOrder();
0337 
0338     // determine order of credit and debit edit widgets
0339     // based on their visual order in the ledger
0340     int creditColumn = JournalModel::Column::Payment;
0341     int debitColumn = JournalModel::Column::Deposit;
0342 
0343     QWidget* w(this);
0344     do {
0345         w = w->parentWidget();
0346         const auto view = qobject_cast<const QTableView*>(w);
0347         if (view) {
0348             creditColumn = view->horizontalHeader()->visualIndex(creditColumn);
0349             debitColumn = view->horizontalHeader()->visualIndex(debitColumn);
0350             break;
0351         }
0352     } while (w);
0353 
0354     // in case they are in the opposite order, we swap the edit widgets
0355     if (debitColumn < creditColumn) {
0356         d->ui->creditDebitEdit->swapCreditDebit();
0357     }
0358 
0359     d->accountsModel->addAccountGroup(QVector<eMyMoney::Account::Type>{
0360         eMyMoney::Account::Type::Asset,
0361         eMyMoney::Account::Type::Liability,
0362         eMyMoney::Account::Type::Equity,
0363     });
0364     d->accountsModel->setHideEquityAccounts(false);
0365     d->accountsModel->setHideZeroBalancedEquityAccounts(false);
0366     d->accountsModel->setHideZeroBalancedAccounts(false);
0367     d->accountsModel->setShowAllEntries(KMyMoneySettings::showAllAccounts());
0368     d->accountsModel->setSourceModel(model);
0369     d->accountsModel->sort(AccountsModel::Column::AccountName);
0370     d->ui->accountCombo->setModel(d->accountsModel);
0371 
0372     d->categoriesModel->addAccountGroup(QVector<eMyMoney::Account::Type>{
0373         eMyMoney::Account::Type::Asset,
0374         eMyMoney::Account::Type::Liability,
0375         eMyMoney::Account::Type::Income,
0376         eMyMoney::Account::Type::Expense,
0377         eMyMoney::Account::Type::Equity,
0378     });
0379     d->categoriesModel->setHideEquityAccounts(false);
0380     d->categoriesModel->setShowAllEntries(KMyMoneySettings::showAllAccounts());
0381     d->categoriesModel->setSourceModel(model);
0382     d->categoriesModel->sort(AccountsModel::Column::AccountName);
0383     d->ui->categoryCombo->setModel(d->categoriesModel);
0384 
0385     d->ui->tagContainer->setModel(file->tagsModel()->modelWithEmptyItem());
0386 
0387     d->costCenterModel->setSortRole(Qt::DisplayRole);
0388     d->costCenterModel->setSourceModel(file->costCenterModel()->modelWithEmptyItem());
0389     d->costCenterModel->setSortLocaleAware(true);
0390     d->costCenterModel->sort(0);
0391 
0392     d->ui->costCenterCombo->setEditable(true);
0393     d->ui->costCenterCombo->setModel(d->costCenterModel);
0394     d->ui->costCenterCombo->setModelColumn(0);
0395     d->ui->costCenterCombo->completer()->setFilterMode(Qt::MatchContains);
0396 
0397     d->payeesModel->setSortRole(Qt::DisplayRole);
0398     d->payeesModel->setSourceModel(file->payeesModel()->modelWithEmptyItem());
0399     d->payeesModel->setSortLocaleAware(true);
0400     d->payeesModel->sort(0);
0401 
0402     d->ui->payeeEdit->setEditable(true);
0403     d->ui->payeeEdit->lineEdit()->setClearButtonEnabled(true);
0404     d->ui->payeeEdit->setModel(d->payeesModel);
0405     d->ui->payeeEdit->setModelColumn(0);
0406     d->ui->payeeEdit->completer()->setCompletionMode(QCompleter::PopupCompletion);
0407     d->ui->payeeEdit->completer()->setFilterMode(Qt::MatchContains);
0408 
0409     // make sure that there is no selection left in the background
0410     // in case there is no text in the edit field
0411     connect(d->ui->payeeEdit->lineEdit(), &QLineEdit::textEdited, [&](const QString& txt) {
0412         if (txt.isEmpty()) {
0413             d->ui->payeeEdit->setCurrentIndex(-1);
0414         }
0415     });
0416 
0417     connect(d->ui->categoryCombo->lineEdit(), &QLineEdit::textEdited, [&](const QString& txt) {
0418         if (txt.isEmpty()) {
0419             d->ui->categoryCombo->setSelected(QString());
0420         }
0421     });
0422     d->ui->enterButton->setIcon(Icons::get(Icon::DialogOK));
0423     d->ui->cancelButton->setIcon(Icons::get(Icon::DialogCancel));
0424 
0425     // construct a special status model that supports the unchanged (unknown) entry
0426     QMap<QString, StatusEntry> states = {
0427         {QStringLiteral("ST00"), StatusEntry(QString(), eMyMoney::Split::State::Unknown, QString(), QString())},
0428     };
0429     const auto rows = d->statusModel.rowCount();
0430     for (int row = 0; row < rows; ++row) {
0431         const auto idx = d->statusModel.index(row, 0);
0432         const auto statusEntry = d->statusModel.itemByIndex(idx);
0433         states.insert(statusEntry.id(), statusEntry);
0434     }
0435     d->statusModel.load(states);
0436 
0437     d->ui->statusCombo->setModel(&d->statusModel);
0438 
0439     d->ui->creditDebitEdit->setAllowEmpty(true);
0440 
0441     d->frameCollection = new WidgetHintFrameCollection(this);
0442     d->frameCollection->addFrame(new WidgetHintFrame(d->ui->dateEdit));
0443     d->frameCollection->addFrame(new WidgetHintFrame(d->ui->categoryCombo));
0444     d->frameCollection->addFrame(new WidgetHintFrame(d->ui->tagContainer));
0445     d->frameCollection->addFrame(new WidgetHintFrame(d->ui->costCenterCombo));
0446     d->frameCollection->addFrame(new WidgetHintFrame(d->ui->numberEdit, WidgetHintFrame::Warning));
0447     d->frameCollection->addWidget(d->ui->enterButton);
0448 
0449     connect(d->ui->numberEdit, &QLineEdit::textChanged, this, [&](const QString& newNumber) {
0450         d->numberChanged(newNumber);
0451     });
0452 
0453     connect(d->ui->costCenterCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [&](int costCenterIndex) {
0454         d->costCenterChanged(costCenterIndex);
0455     });
0456 
0457     connect(d->ui->categoryCombo, &KMyMoneyAccountCombo::accountSelected, this, [&](const QString& id) {
0458         d->categoryChanged(id);
0459     });
0460 
0461     connect(d->ui->dateEdit, &KMyMoneyDateEdit::dateValidityChanged, this, [&](const QDate& date) {
0462         d->postdateChanged(date);
0463     });
0464 
0465     connect(d->ui->dateEdit, &KMyMoneyDateEdit::dateEntered, this, [&](const QDate& date) {
0466         d->postdateChanged(date);
0467         Q_EMIT postDateChanged(date);
0468     });
0469 
0470     connect(d->ui->creditDebitEdit, &CreditDebitEdit::amountChanged, this, [&]() {
0471         d->amountChanged();
0472     });
0473 
0474     connect(d->ui->payeeEdit, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [&](int payeeIndex) {
0475         d->payeeChanged(payeeIndex);
0476     });
0477 
0478     connect(d->ui->tagContainer, &KTagContainer::tagsChanged, this, [&](const QStringList& tagIds) {
0479         d->tagsChanged(tagIds);
0480     });
0481 
0482     connect(d->ui->cancelButton, &QToolButton::clicked, this, &MultiTransactionEditor::reject);
0483     connect(d->ui->enterButton, &QToolButton::clicked, this, &MultiTransactionEditor::acceptEdit);
0484 
0485     // handle some events in certain conditions different from default
0486     d->ui->payeeEdit->installEventFilter(this);
0487     d->ui->costCenterCombo->installEventFilter(this);
0488     d->ui->tagContainer->tagCombo()->installEventFilter(this);
0489     d->ui->categoryCombo->installEventFilter(this);
0490     d->ui->statusCombo->installEventFilter(this);
0491 
0492     setCancelButton(d->ui->cancelButton);
0493     setEnterButton(d->ui->enterButton);
0494 
0495     // force setup of filters
0496     slotSettingsChanged();
0497 
0498     // setup widget content
0499     d->ui->dateEdit->setAllowEmptyDate(true);
0500     d->ui->dateEdit->clearEditText();
0501 
0502     d->ui->creditDebitEdit->setAllowEmpty(true);
0503 
0504     d->ui->categoryCombo->setSplitActionVisible(false);
0505     d->ui->categoryCombo->clearSelection();
0506 }
0507 
0508 MultiTransactionEditor::~MultiTransactionEditor()
0509 {
0510 }
0511 
0512 void MultiTransactionEditor::setAmountPlaceHolderText(const QAbstractItemModel* model)
0513 {
0514     d->ui->creditDebitEdit->setPlaceholderText(model->headerData(JournalModel::Column::Payment, Qt::Horizontal).toString(),
0515                                                model->headerData(JournalModel::Column::Deposit, Qt::Horizontal).toString());
0516 }
0517 
0518 void MultiTransactionEditor::loadTransaction(const QModelIndex& index)
0519 {
0520     Q_UNUSED(index)
0521 }
0522 
0523 QStringList MultiTransactionEditor::saveTransaction(const QStringList& selectedJournalEntryIds)
0524 {
0525     auto selection(selectedJournalEntryIds);
0526     connect(MyMoneyFile::instance()->journalModel(), &JournalModel::idChanged, this, [&](const QString& currentId, const QString& previousId) {
0527         selection.replaceInStrings(previousId, currentId);
0528     });
0529 
0530     const auto file = MyMoneyFile::instance();
0531 
0532     auto splitsUseSameCurrency = [&](const MyMoneySplit& split1, const MyMoneySplit& split2) {
0533         const auto accountsModel = file->accountsModel();
0534         const auto accIdx1 = accountsModel->indexById(split1.accountId());
0535         const auto accIdx2 = accountsModel->indexById(split2.accountId());
0536         const auto secId1 = accIdx1.data(eMyMoney::Model::AccountCurrencyIdRole).toString();
0537         const auto secId2 = accIdx2.data(eMyMoney::Model::AccountCurrencyIdRole).toString();
0538         return secId1 == secId2;
0539     };
0540 
0541     if (d->anyChanges()) {
0542         const auto journalModel = MyMoneyFile::instance()->journalModel();
0543         MyMoneyFileTransaction ft;
0544         try {
0545             for (const auto& journalEntryId : selectedJournalEntryIds) {
0546                 const auto journalIdx = journalModel->indexById(journalEntryId);
0547                 const auto journalEntry = journalModel->itemByIndex(journalIdx);
0548                 auto t = journalEntry.transaction();
0549                 auto sp = journalEntry.split();
0550                 MyMoneySplit csp;
0551 
0552                 if (t.splitCount() == 2) {
0553                     for (auto split : t.splits()) {
0554                         if (split.id() != sp.id()) {
0555                             csp = split;
0556                             break;
0557                         }
0558                     }
0559                 }
0560 
0561                 if (!d->ui->dateEdit->isNull()) {
0562                     t.setPostDate(d->ui->dateEdit->date());
0563                 }
0564                 if (!d->ui->statusCombo->currentText().isEmpty()) {
0565                     const auto idx = d->statusModel.index(d->ui->statusCombo->currentIndex(), 0);
0566                     sp.setReconcileFlag(idx.data(eMyMoney::Model::SplitReconcileStateRole).value<eMyMoney::Split::State>());
0567                     t.modifySplit(sp);
0568                 }
0569                 if (!d->ui->payeeEdit->lineEdit()->text().isEmpty()) {
0570                     const auto payeeRow = d->ui->payeeEdit->currentIndex();
0571                     const auto payeeIdx = d->payeesModel->index(payeeRow, 0);
0572                     const auto payeeId = payeeIdx.data(eMyMoney::Model::IdRole).toString();
0573                     sp.setPayeeId(payeeId);
0574                     t.modifySplit(sp);
0575                     if (!csp.id().isEmpty()) {
0576                         csp.setPayeeId(payeeId);
0577                         t.modifySplit(csp);
0578                     }
0579                 }
0580                 if (!d->ui->categoryCombo->getSelected().isEmpty()) {
0581                     if (!csp.id().isEmpty()) {
0582                         csp.setAccountId(d->ui->categoryCombo->getSelected());
0583                         t.modifySplit(csp);
0584                     } else {
0585                         csp = sp;
0586                         csp.clearId();
0587                         csp.setValue(-csp.value());
0588                         /// @todo make assignment multi currency safe. For now, we
0589                         /// just dump out a debug message in case both securities are
0590                         /// not the same
0591                         csp.setShares(-csp.shares());
0592                         csp.setAccountId(d->ui->categoryCombo->getSelected());
0593                         if (!splitsUseSameCurrency(sp, csp)) {
0594                             qDebug() << "MultiTransactionEditor does not support multiple currencies yet";
0595                         }
0596                         csp.setReconcileDate(QDate());
0597                         csp.setReconcileFlag(eMyMoney::Split::State::Unknown);
0598                         csp.setBankID(QString());
0599                         csp.setAction(QString());
0600                         csp.setNumber(QString());
0601                         t.addSplit(csp);
0602                     }
0603                 }
0604 
0605                 file->modifyTransaction(t);
0606             }
0607             ft.commit();
0608         } catch (const MyMoneyException& e) {
0609             qDebug() << Q_FUNC_INFO << "something went wrong" << e.what();
0610             selection = selectedJournalEntryIds;
0611         }
0612     }
0613     return selection;
0614 }
0615 
0616 bool MultiTransactionEditor::eventFilter(QObject* o, QEvent* e)
0617 {
0618     auto cb = qobject_cast<QComboBox*>(o);
0619     if (cb) {
0620         // filter out wheel events for combo boxes if the popup view is not visible
0621         if ((e->type() == QEvent::Wheel) && !cb->view()->isVisible()) {
0622             return true;
0623         }
0624 
0625         if (e->type() == QEvent::FocusOut) {
0626             if (cb == d->ui->categoryCombo) {
0627                 if (needCreateCategory(d->ui->categoryCombo)) {
0628                     createCategory(d->ui->categoryCombo, defaultCategoryType(d->ui->creditDebitEdit));
0629                 }
0630 
0631             } else if (cb == d->ui->payeeEdit) {
0632                 if (needCreatePayee(cb)) {
0633                     createPayee(cb);
0634                 }
0635             }
0636         }
0637     }
0638     return QWidget::eventFilter(o, e);
0639 }
0640 
0641 QDate MultiTransactionEditor::postDate() const
0642 {
0643     return d->ui->dateEdit->date();
0644 }
0645 
0646 void MultiTransactionEditor::setShowAccountCombo(bool show) const
0647 {
0648     d->ui->accountLabel->setVisible(show);
0649     d->ui->accountCombo->setVisible(show);
0650     d->ui->topMarginWidget->setVisible(show);
0651     d->ui->accountCombo->setSplitActionVisible(false);
0652 }
0653 
0654 void MultiTransactionEditor::setShowButtons(bool show) const
0655 {
0656     d->ui->enterButton->setVisible(show);
0657     d->ui->cancelButton->setVisible(show);
0658 }
0659 
0660 void MultiTransactionEditor::setShowNumberWidget(bool show) const
0661 {
0662     d->ui->numberLabel->setVisible(show);
0663     d->ui->numberEdit->setVisible(show);
0664 }
0665 
0666 void MultiTransactionEditor::setAccountId(const QString& accountId)
0667 {
0668     d->ui->accountCombo->setSelected(accountId);
0669 }
0670 
0671 void MultiTransactionEditor::setReadOnly(bool readOnly)
0672 {
0673     if (isReadOnly() != readOnly) {
0674         TransactionEditorBase::setReadOnly(readOnly);
0675         if (readOnly) {
0676             d->frameCollection->removeWidget(d->ui->enterButton);
0677             d->ui->enterButton->setDisabled(true);
0678         } else {
0679             // no need to enable the enter button here as the
0680             // frameCollection will take care of it anyway
0681             d->frameCollection->addWidget(d->ui->enterButton);
0682         }
0683     }
0684 }
0685 
0686 void MultiTransactionEditor::setupUi(QWidget* parent)
0687 {
0688     if (d->tabOrderUi == nullptr) {
0689         d->tabOrderUi = new Ui::NewTransactionEditor;
0690     }
0691     d->tabOrderUi->setupUi(parent);
0692     d->tabOrderUi->accountLabel->setVisible(false);
0693     d->tabOrderUi->accountCombo->setVisible(false);
0694 }
0695 
0696 void MultiTransactionEditor::storeTabOrder(const QStringList& tabOrder)
0697 {
0698     TransactionEditorBase::storeTabOrder(QLatin1String("multiTransactionEditor"), tabOrder);
0699 }
0700 
0701 void MultiTransactionEditor::slotSettingsChanged()
0702 {
0703     d->categoriesModel->setShowAllEntries(KMyMoneySettings::showAllAccounts());
0704     d->accountsModel->setShowAllEntries(KMyMoneySettings::showAllAccounts());
0705 }
0706 
0707 bool MultiTransactionEditor::setSelectedJournalEntryIds(const QStringList& selectedJournalEntryIds)
0708 {
0709     d->selectedJournalEntryIds = selectedJournalEntryIds;
0710     const auto journalModel = MyMoneyFile::instance()->journalModel();
0711     bool payeeEnabled(true);
0712     bool categoryEnabled(true);
0713     bool amountEnabled(true);
0714     bool costCenterEnabled(true);
0715     bool tagEnabled(true);
0716     bool memoEnabled(true);
0717     bool statusEnabled(true);
0718 
0719     for (const auto& journalEntryId : selectedJournalEntryIds) {
0720         const auto idx = journalModel->indexById(journalEntryId);
0721         if (idx.data(eMyMoney::Model::BaseModelRole) == eMyMoney::Model::SchedulesJournalEntryRole) {
0722             d->errorMessage = i18nc("@info Error selecting multiple transactions for edit", "Cannot edit multiple schedules at once.");
0723             return false;
0724         }
0725         if (idx.data(eMyMoney::Model::JournalEntryIsFrozenRole).toBool() == true) {
0726             statusEnabled = false;
0727         }
0728         if (idx.data(eMyMoney::Model::TransactionSplitCountRole).toInt() > 2) {
0729             WidgetHintFrame::hide(d->ui->categoryCombo,
0730                                   i18nc("@info:tooltip selecting multiple transactions for edit",
0731                                         "Selection of category or account is not possible when split transactions are part of the selection."));
0732             categoryEnabled = false;
0733             WidgetHintFrame::hide(d->ui->tagContainer,
0734                                   i18nc("@info:tooltip selecting multiple transactions for edit",
0735                                         "Selection of tags is not possible when split transactions are part of the selection."));
0736             tagEnabled = false;
0737             WidgetHintFrame::hide(d->ui->costCenterCombo,
0738                                   i18nc("@info:tooltip selecting multiple transactions for edit",
0739                                         "Selection of cost center is not possible when split transactions are part of the selection."));
0740             costCenterEnabled = false;
0741         }
0742     }
0743 
0744     // now disable the widgets which the user cannot use due to selected transactions
0745     d->ui->dateEdit->setEnabled(true);
0746     d->ui->creditDebitEdit->setEnabled(amountEnabled);
0747     d->ui->payeeEdit->setEnabled(payeeEnabled);
0748     d->ui->numberEdit->setEnabled(false);
0749     d->ui->categoryCombo->setEnabled(categoryEnabled);
0750     d->ui->costCenterCombo->setEnabled(costCenterEnabled);
0751     d->ui->tagContainer->setEnabled(tagEnabled);
0752     d->ui->statusCombo->setEnabled(statusEnabled);
0753     d->ui->memoEdit->setEnabled(memoEnabled);
0754     d->ui->enterButton->setEnabled(true);
0755     return true;
0756 }
0757 
0758 QString MultiTransactionEditor::errorMessage() const
0759 {
0760     return d->errorMessage;
0761 }
0762 
0763 bool MultiTransactionEditor::isTransactionDataValid() const
0764 {
0765     return true;
0766 }