/*
    SPDX-FileCopyrightText: 2007-2018 Thomas Baumgart <tbaumgart@kde.org>
    SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "transactioneditor.h"
#include "transactioneditor_p.h"

// ----------------------------------------------------------------------------
// QT Includes

#include <QApplication>
#include <QEventLoop>
#include <QKeyEvent>
#include <QList>
#include <QEvent>
#include <QIcon>
#include <QTimer>

// ----------------------------------------------------------------------------
// KDE Includes

#include <KTextEdit>
#include <KLocalizedString>
#include <KMessageBox>
#include <KStandardGuiItem>
#include <KGuiItem>

// ----------------------------------------------------------------------------
// Project Includes

#include "kmymoneytagcombo.h"
#include "knewinvestmentwizard.h"
#include "knewaccountdlg.h"
#include "ktagcontainer.h"
#include "tabbar.h"
#include "mymoneyutils.h"
#include "mymoneyexception.h"
#include "kmymoneycategory.h"
#include "kmymoneymvccombo.h"
#include "amountedit.h"
#include "kmymoneylineedit.h"
#include "mymoneyfile.h"
#include "mymoneyprice.h"
#include "mymoneysecurity.h"
#include "kmymoneyutils.h"
#include "kmymoneycompletion.h"
#include "transaction.h"
#include "transactionform.h"
#include "kmymoneysettings.h"
#include "transactioneditorcontainer.h"

#include "kcurrencycalculator.h"
#include "icons.h"

using namespace KMyMoneyRegister;
using namespace KMyMoneyTransactionForm;
using namespace Icons;

TransactionEditor::TransactionEditor() :
    d_ptr(new TransactionEditorPrivate(this))
{
    Q_D(TransactionEditor);
    d->init();
}

TransactionEditor::TransactionEditor(TransactionEditorPrivate &dd,
                                     TransactionEditorContainer* regForm,
                                     KMyMoneyRegister::Transaction* item,
                                     const KMyMoneyRegister::SelectedTransactions& list,
                                     const QDate& lastPostDate) :
    d_ptr(&dd)
//  d_ptr(new TransactionEditorPrivate)
{
    Q_D(TransactionEditor);
    d->m_paymentMethod = eMyMoney::Schedule::PaymentType::Any;
    d->m_transactions = list;
    d->m_regForm = regForm;
    d->m_item = item;
    d->m_transaction = item->transaction();
    d->m_split = item->split();
    d->m_lastPostDate = lastPostDate;
    d->m_initialAction = eWidgets::eRegister::Action::None;
    d->m_openEditSplits = false;
    d->m_memoChanged = false;
    d->m_item->startEditMode();
    connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, this, static_cast<void (TransactionEditor::*)()>(&TransactionEditor::slotUpdateAccount));
}

TransactionEditor::TransactionEditor(TransactionEditorPrivate &dd) :
    d_ptr(&dd)
{
    Q_D(TransactionEditor);
    d->init();
}

TransactionEditor::~TransactionEditor()
{
    Q_D(TransactionEditor);
    // Make sure the widgets do not send out signals to the editor anymore
    // After all, the editor is about to die

    //disconnect first tagCombo:
    auto w = dynamic_cast<KTagContainer*>(haveWidget("tag"));
    if (w && w->tagCombo()) {
        w->tagCombo()->disconnect(this);
    }

    QMap<QString, QWidget*>::iterator it_w;
    for (it_w = d->m_editWidgets.begin(); it_w != d->m_editWidgets.end(); ++it_w) {
        (*it_w)->disconnect(this);
    }

    d->m_regForm->removeEditWidgets(d->m_editWidgets);
    d->m_item->leaveEditMode();
    emit finishEdit(d->m_transactions);
}

void TransactionEditor::slotUpdateAccount(const QString& id)
{
    Q_D(TransactionEditor);
    d->m_account = MyMoneyFile::instance()->account(id);
    setupPrecision();
}

void TransactionEditor::slotUpdateAccount()
{
    Q_D(TransactionEditor);
    // reload m_account as it might have been changed
    d->m_account = MyMoneyFile::instance()->account(d->m_account.id());
    setupPrecision();
}

void TransactionEditor::setupPrecision()
{
    Q_D(TransactionEditor);
    const int prec = (d->m_account.id().isEmpty()) ? 2 : MyMoneyMoney::denomToPrec(d->m_account.fraction());
    QStringList widgets = QString("amount,deposit,payment").split(',');
    QStringList::const_iterator it_w;
    for (it_w = widgets.constBegin(); it_w != widgets.constEnd(); ++it_w) {
        QWidget * w;
        if ((w = haveWidget(*it_w)) != 0) {
            if (auto precisionWidget = dynamic_cast<AmountEdit*>(w))
                precisionWidget->setPrecision(prec);
        }
    }
}

void TransactionEditor::setup(QWidgetList& tabOrderWidgets, const MyMoneyAccount& account, eWidgets::eRegister::Action action)
{
    Q_D(TransactionEditor);
    d->m_account = account;
    d->m_initialAction = action;
    createEditWidgets();
    d->m_regForm->arrangeEditWidgets(d->m_editWidgets, d->m_item);
    d->m_regForm->tabOrder(tabOrderWidgets, d->m_item);
    QWidget* w = haveWidget("tabbar");
    if (w) {
        tabOrderWidgets.append(w);
        auto tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(w);
        if ((tabbar) && (action == eWidgets::eRegister::Action::None)) {
            action = static_cast<eWidgets::eRegister::Action>(tabbar->currentIndex());
        }
    }
    loadEditWidgets(action);

    // remove all unused widgets and don't forget to remove them
    // from the tab order list as well
    d->m_editWidgets.removeOrphans();
    QWidgetList::iterator it_w;
    const QWidgetList editWidgets(d->m_editWidgets.values());
    for (it_w = tabOrderWidgets.begin(); it_w != tabOrderWidgets.end();) {
        if (editWidgets.contains(*it_w)) {
            ++it_w;
        } else {
            // before we remove the widget, we make sure it's not a part of a known one.
            // these could be a direct child in case of KMyMoneyDateInput and AmountEdit
            // where we store the pointer to the surrounding frame in editWidgets
            // or the parent is called "KMyMoneyCategoryFrame"
            if (*it_w) {
                if (editWidgets.contains((*it_w)->parentWidget())
                        || ((*it_w)->parentWidget() && (*it_w)->parentWidget()->objectName() == QLatin1String("KMyMoneyCategoryFrame"))) {
                    ++it_w;

                } else {
                    // qDebug("Remove '%s' from taborder", qPrintable((*it_w)->objectName()));
                    it_w = tabOrderWidgets.erase(it_w);
                }
            } else {
                it_w = tabOrderWidgets.erase(it_w);
            }
        }
    }

    clearFinalWidgets();
    setupFinalWidgets();
    slotUpdateButtonState();
}

void TransactionEditor::setup(QWidgetList& tabOrderWidgets, const MyMoneyAccount& account)
{
    setup(tabOrderWidgets, account, eWidgets::eRegister::Action::None);
}

MyMoneyAccount TransactionEditor::account() const
{
    Q_D(const TransactionEditor);
    return d->m_account;
}

void TransactionEditor::setScheduleInfo(const QString& si)
{
    Q_D(TransactionEditor);
    d->m_scheduleInfo = si;
}

void TransactionEditor::setPaymentMethod(eMyMoney::Schedule::PaymentType pm)
{
    Q_D(TransactionEditor);
    d->m_paymentMethod = pm;
}

void TransactionEditor::clearFinalWidgets()
{
    Q_D(TransactionEditor);
    d->m_finalEditWidgets.clear();
}

void TransactionEditor::addFinalWidget(const QWidget* w)
{
    Q_D(TransactionEditor);
    if (w) {
        d->m_finalEditWidgets << w;
    }
}

void TransactionEditor::slotReloadEditWidgets()
{
}

bool TransactionEditor::eventFilter(QObject* o, QEvent* e)
{
    Q_D(TransactionEditor);
    bool rc = false;
    if (o == haveWidget("number")) {
        if (e->type() == QEvent::MouseButtonDblClick) {
            assignNextNumber();
            rc = true;
        }
    }

    // if the object is a widget, the event is a key press event and
    // the object is one of our edit widgets, then ....
    auto numberWiget = dynamic_cast<QWidget*>(o);
    if (o->isWidgetType()
            && (e->type() == QEvent::KeyPress)
            && numberWiget && d->m_editWidgets.values().contains(numberWiget)) {
        auto k = dynamic_cast<QKeyEvent*>(e);
        if (k && (((k->modifiers() & Qt::KeyboardModifierMask) == 0)
                  || ((k->modifiers() & Qt::KeypadModifier) != 0))) {
            bool isFinal = false;
            QList<const QWidget*>::const_iterator it_w;
            switch (k->key()) {
            case Qt::Key_Return:
            case Qt::Key_Enter:
                // we check, if the object is one of the m_finalEditWidgets and if it's
                // a AmountEdit object that the value is not 0. If any of that is the
                // case, it's the final object. In other cases, we convert the enter
                // key into a TAB key to move between the fields. Of course, we only need
                // to do this as long as the appropriate option is set. In all other cases,
                // we treat the return/enter key as such.
                if (KMyMoneySettings::enterMovesBetweenFields()) {
                    for (it_w = d->m_finalEditWidgets.constBegin(); !isFinal && it_w != d->m_finalEditWidgets.constEnd(); ++it_w) {
                        if (*it_w == o) {
                            if (auto widget = dynamic_cast<const AmountEdit*>(*it_w)) {
                                isFinal = !(widget->value().isZero());
                            } else
                                isFinal = true;
                        }
                    }
                } else
                    isFinal = true;

                // for the non-final objects, we treat the return key as a TAB
                if (!isFinal) {
                    QKeyEvent evt(e->type(),
                                  Qt::Key_Tab, k->modifiers(), QString(),
                                  k->isAutoRepeat(), k->count());

                    QApplication::sendEvent(o, &evt);
                    // in case of a category item and the split button is visible
                    // send a second event so that we get passed the button.
                    auto widget = dynamic_cast<KMyMoneyCategory*>(o);
                    if (widget && widget->splitButton())
                        QApplication::sendEvent(o, &evt);

                } else if (!d->m_readOnly) {
                    QTimer::singleShot(0, this, SIGNAL(returnPressed()));
                }
                // don't process any further
                rc = true;
                break;

            case Qt::Key_Escape:
                QTimer::singleShot(0, this, SIGNAL(escapePressed()));
                break;
            }
        }
    }
    return rc;
}

void TransactionEditor::slotUpdateMemoState()
{
    Q_D(TransactionEditor);
    KTextEdit* memo = dynamic_cast<KTextEdit*>(d->m_editWidgets["memo"]);
    if (memo) {
        d->m_memoChanged = (memo->toPlainText() != d->m_memoText);
    }
}

void TransactionEditor::slotUpdateButtonState()
{
    QString reason;
    emit transactionDataSufficient(isComplete(reason));
}

QWidget* TransactionEditor::haveWidget(const QString& name) const
{
    Q_D(const TransactionEditor);
    return d->m_editWidgets.haveWidget(name);
}

int TransactionEditor::slotEditSplits()
{
    return QDialog::Rejected;
}

void TransactionEditor::setTransaction(const MyMoneyTransaction& t, const MyMoneySplit& s)
{
    Q_D(TransactionEditor);
    d->m_transaction = t;
    d->m_split = s;
    loadEditWidgets();
}

bool TransactionEditor::isMultiSelection() const
{
    Q_D(const TransactionEditor);
    return d->m_transactions.count() > 1;
}

bool TransactionEditor::fixTransactionCommodity(const MyMoneyAccount& account)
{
    Q_D(TransactionEditor);
    bool rc = true;
    bool firstTimeMultiCurrency = true;
    d->m_account = account;

    auto file = MyMoneyFile::instance();

    // determine the max fraction for this account
    MyMoneySecurity sec = file->security(d->m_account.currencyId());
    int fract = d->m_account.fraction();

    // scan the list of selected transactions
    KMyMoneyRegister::SelectedTransactions::iterator it_t;
    for (it_t = d->m_transactions.begin(); (rc == true) && (it_t != d->m_transactions.end()); ++it_t) {
        // there was a time when the schedule editor did not setup the transaction commodity
        // let's give a helping hand here for those old schedules
        if ((*it_t).transaction().commodity().isEmpty())
            (*it_t).transaction().setCommodity(d->m_account.currencyId());
        // we need to check things only if a different commodity is used
        if (d->m_account.currencyId() != (*it_t).transaction().commodity()) {
            MyMoneySecurity osec = file->security((*it_t).transaction().commodity());
            switch ((*it_t).transaction().splitCount()) {
            case 0:
                // new transaction, guess nothing's here yet ;)
                break;

            case 1:
                try {
                    // make sure, that the value is equal to the shares, don't forget our own copy
                    MyMoneySplit& splitB = (*it_t).split();  // reference usage wanted here
                    if (d->m_split == splitB)
                        d->m_split.setValue(splitB.shares());
                    splitB.setValue(splitB.shares());
                    (*it_t).transaction().modifySplit(splitB);

                } catch (const MyMoneyException &e) {
                    qDebug("Unable to update commodity to second splits currency in %s: '%s'", qPrintable((*it_t).transaction().id()), e.what());
                }
                break;

            case 2:
                // If we deal with multiple currencies we make sure, that for
                // transactions with two splits, the transaction's commodity is the
                // currency of the currently selected account. This saves us from a
                // lot of grieve later on.  We just have to switch the
                // transactions commodity. Let's assume the following scenario:
                // - transactions commodity is CA
                // - splitB and account's currencyId is CB
                // - splitA is of course in CA (otherwise we have a real problem)
                // - Value is V in both splits
                // - Shares in splitB is SB
                // - Shares in splitA is SA (and equal to V)
                //
                // We do the following:
                // - change transactions commodity to CB
                // - set V in both splits to SB
                // - modify the splits in the transaction
                try {
                    // retrieve the splits
                    MyMoneySplit& splitB = (*it_t).split();  // reference usage wanted here
                    MyMoneySplit splitA = (*it_t).transaction().splitByAccount(d->m_account.id(), false);

                    // - set V in both splits to SB. Don't forget our own copy
                    if (d->m_split == splitB) {
                        d->m_split.setValue(splitB.shares());
                    }
                    splitB.setValue(splitB.shares());
                    splitA.setValue(-splitB.shares());
                    (*it_t).transaction().modifySplit(splitA);
                    (*it_t).transaction().modifySplit(splitB);

                } catch (const MyMoneyException &e) {
                    qDebug("Unable to update commodity to second splits currency in %s: '%s'", qPrintable((*it_t).transaction().id()), e.what());
                }
                break;

            default:
                // TODO: use new logic by adjusting all splits by the price
                // extracted from the selected split. Inform the user that
                // this will happen and allow him to stop the processing (rc = false)

                try {
                    QString msg;
                    if (firstTimeMultiCurrency) {
                        firstTimeMultiCurrency = false;
                        if (!isMultiSelection()) {
                            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());
                        } else {
                            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());
                        }

                        if (KMessageBox::warningContinueCancel(0, QString("<qt>%1</qt>").arg(msg)) == KMessageBox::Cancel) {
                            rc = false;
                        }
                    }

                    if (rc == true) {
                        MyMoneyMoney price;
                        if (!(*it_t).split().shares().isZero() && !(*it_t).split().value().isZero())
                            price = (*it_t).split().shares() / (*it_t).split().value();
                        MyMoneySplit& mySplit = (*it_t).split();
                        foreach (const auto split, (*it_t).transaction().splits()) {
                            auto s = split;
                            if (s == mySplit) {
                                s.setValue(s.shares());
                                if (mySplit == d->m_split) {
                                    d->m_split = s;
                                }
                                mySplit = s;
                            } else {
                                s.setValue((s.value() * price).convert(fract));
                            }
                            (*it_t).transaction().modifySplit(s);
                        }
                    }
                } catch (const MyMoneyException &e) {
                    qDebug("Unable to update commodity of split currency in %s: '%s'", qPrintable((*it_t).transaction().id()), e.what());
                }
                break;
            }

            // set the transaction's ommodity to this account's currency
            (*it_t).transaction().setCommodity(d->m_account.currencyId());

            // update our copy of the transaction that has the focus
            if ((*it_t).transaction().id() == d->m_transaction.id()) {
                d->m_transaction = (*it_t).transaction();
            }
        }
    }
    return rc;
}

QString TransactionEditor::validateCheckNumber(const QString& num) const
{
    Q_D(const TransactionEditor);

    int rc = KMessageBox::No;
    QString schedInfo;
    if (!d->m_scheduleInfo.isEmpty()) {
        schedInfo = i18n("<center>Processing schedule for %1.</center>", d->m_scheduleInfo);
    }
    if (MyMoneyFile::instance()->checkNoUsed(d->m_account.id(), num)) {
        rc = KMessageBox::questionYesNo(d->m_regForm, QString("<qt>") + schedInfo + i18n("Check number <b>%1</b> has already been used in account <b>%2</b>."
                                        "<center>Do you want to replace it with the next available number?</center>", num, d->m_account.name()) + QString("</qt>"), i18n("Duplicate number"));
        if (rc == KMessageBox::Yes) {
            return KMyMoneyUtils::nextFreeCheckNumber(d->m_account);
        }
    }
    return num;
}

void TransactionEditor::assignNextNumber()
{
    Q_D(TransactionEditor);
    auto number = dynamic_cast<KMyMoneyLineEdit*>(haveWidget("number"));
    if (number) {
        const auto num = validateCheckNumber(KMyMoneyUtils::nextCheckNumber(d->m_account));
        d->m_account.setValue("lastNumberUsed", num);
        number->setText(num);
    }
}

void TransactionEditor::slotNumberChanged(const QString& txt)
{
    auto number = dynamic_cast<KMyMoneyLineEdit*>(haveWidget("number"));
    if (number) {
        const auto next = validateCheckNumber(txt);
        if (next != txt) {
            number->setText(next);
        }
    }
}


bool TransactionEditor::canAssignNumber() const
{
    if (dynamic_cast<KMyMoneyLineEdit*>(haveWidget("number")))
        return true;
    return false;
}

void TransactionEditor::setupCategoryWidget(KMyMoneyCategory* category, const QList<MyMoneySplit>& splits, QString& categoryId, const char* splitEditSlot, bool /* allowObjectCreation */)
{
    disconnect(category, SIGNAL(focusIn()), this, splitEditSlot);
#if 0
    // FIXME must deal with the logic that suppressObjectCreation is
    // automatically turned off when the createItem() signal is connected
    if (allowObjectCreation)
        category->setSuppressObjectCreation(false);
#endif

    switch (splits.count()) {
    case 0:
        categoryId.clear();
        if (!category->currentText().isEmpty()) {
            //   category->clearEditText();  //  don't clear as could be from another widget - Bug 322768
            // make sure, we don't see the selector
            category->completion()->hide();
        }
        category->completion()->setSelected(QString());
        break;

    case 1:
        categoryId = splits[0].accountId();
        category->completion()->setSelected(categoryId);
        category->slotItemSelected(categoryId);
        break;

    default:
        categoryId.clear();
        category->setSplitTransaction();
        connect(category, SIGNAL(focusIn()), this, splitEditSlot);
#if 0
        // FIXME must deal with the logic that suppressObjectCreation is
        // automatically turned off when the createItem() signal is connected
        if (allowObjectCreation)
            category->setSuppressObjectCreation(true);
#endif
        break;
    }
}

bool TransactionEditor::createNewTransaction() const
{
    Q_D(const TransactionEditor);

    bool rc = true;
    if (!d->m_transactions.isEmpty()) {
        rc = d->m_transactions.at(0).transaction().id().isEmpty();
    }
    return rc;
}

bool TransactionEditor::enterTransactions(QString& newId, bool askForSchedule, bool suppressBalanceWarnings)
{
    Q_D(TransactionEditor);
    newId.clear();
    auto file = MyMoneyFile::instance();

    // make sure to run through all stuff that is tied to 'focusout events'.
    d->m_regForm->parentWidget()->setFocus();
    QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 10);
    // we don't need to update our widgets anymore, so we just disconnect the signal
    disconnect(file, &MyMoneyFile::dataChanged, this, &TransactionEditor::slotReloadEditWidgets);

    KMyMoneyRegister::SelectedTransactions::iterator it_t;
    MyMoneyTransaction t;
    bool newTransactionCreated = false;

    // make sure, that only a single new transaction can be created.
    // we need to update m_transactions to contain the new transaction
    // which is then stored in the variable t when we leave the loop.
    // m_transactions will be sent out in finishEdit() and forces
    // the new transaction to be selected in the ledger view

    // collect the transactions to be stored in the engine in a local
    // list first, so that the user has a chance to interrupt the storage
    // process
    QList<MyMoneyTransaction> list;
    auto storeTransactions = true;

    // collect transactions
    for (it_t = d->m_transactions.begin(); storeTransactions && !newTransactionCreated && it_t != d->m_transactions.end(); ++it_t) {
        storeTransactions = createTransaction(t, (*it_t).transaction(), (*it_t).split());
        // if the transaction was created successfully, append it to the list
        if (storeTransactions)
            list.append(t);

        // if we created a new transaction keep that in mind
        if (t.id().isEmpty())
            newTransactionCreated = true;
    }

    // if not interrupted by user, continue to store them in the engine
    if (storeTransactions) {
        auto i = 0;
        emit statusMsg(i18n("Storing transactions"));
        emit statusProgress(0, list.count());

        MyMoneyFileTransaction ft;

        try {
            QMap<QString, bool> minBalanceEarly;
            QMap<QString, bool> minBalanceAbsolute;
            QMap<QString, bool> maxCreditEarly;
            QMap<QString, bool> maxCreditAbsolute;
            QMap<QString, bool> accountIds;

            for (MyMoneyTransaction& transaction : list) {
                // if we have a categorization, make sure we remove
                // the 'imported' flag automagically
                if (transaction.splitCount() > 1)
                    transaction.setImported(false);

                // create information about min and max balances
                foreach (const auto split, transaction.splits()) {
                    auto acc = file->account(split.accountId());
                    accountIds[acc.id()] = true;
                    MyMoneyMoney balance = file->balance(acc.id());
                    if (!acc.value("minBalanceEarly").isEmpty()) {
                        minBalanceEarly[acc.id()] = balance < MyMoneyMoney(acc.value("minBalanceEarly"));
                    }
                    if (!acc.value("minBalanceAbsolute").isEmpty()) {
                        minBalanceAbsolute[acc.id()] = balance < MyMoneyMoney(acc.value("minBalanceAbsolute"));
                        minBalanceEarly[acc.id()] = false;
                    }
                    if (!acc.value("maxCreditEarly").isEmpty()) {
                        maxCreditEarly[acc.id()] = balance < MyMoneyMoney(acc.value("maxCreditEarly"));
                    }
                    if (!acc.value("maxCreditAbsolute").isEmpty()) {
                        maxCreditAbsolute[acc.id()] = balance < MyMoneyMoney(acc.value("maxCreditAbsolute"));
                        maxCreditEarly[acc.id()] = false;
                    }

                    // and adjust opening date of invest accounts
                    if (acc.isInvest()) {
                        if (acc.openingDate() > t.postDate()) {
                            try {
                                acc.setOpeningDate(t.postDate());
                                file->modifyAccount(acc);
                            } catch(MyMoneyException& ) {
                                qDebug() << "Unable to modify opening date for invest account" << acc.name() << acc.id();
                            }
                        }
                    }
                }

                if (transaction.id().isEmpty()) {
                    bool enter = true;
                    if (askForSchedule && transaction.postDate() > QDate::currentDate()) {
                        KGuiItem enterButton(i18n("&Enter"),
                                             Icons::get(Icon::DialogOK),
                                             i18n("Accepts the entered data and stores it"),
                                             i18n("Use this to enter the transaction into the ledger."));
                        KGuiItem scheduleButton(i18n("&Schedule"),
                                                Icons::get(Icon::NewSchedule),
                                                i18n("Accepts the entered data and stores it as schedule"),
                                                i18n("Use this to schedule the transaction for later entry into the ledger."));

                        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;
                    }
                    if (enter) {
                        // add new transaction
                        file->addTransaction(transaction);
                        // pass the newly assigned id on to the caller
                        newId = transaction.id();
                        // refresh account object for transactional changes
                        // refresh account and transaction object because they might have changed
                        d->m_account = file->account(d->m_account.id());
                        t = transaction;

                        // if a new transaction has a valid number, keep it with the account
                        d->keepNewNumber(transaction);
                    } else {
                        // turn object creation on, so that moving the focus does
                        // not screw up the dialog that might be popping up
                        emit objectCreation(true);
                        emit scheduleTransaction(transaction, eMyMoney::Schedule::Occurrence::Once);
                        emit objectCreation(false);

                        newTransactionCreated = false;
                    }

                    // send out the post date of this transaction
                    emit lastPostDateUsed(transaction.postDate());
                } else {
                    // modify existing transaction
                    // its number might have been edited
                    // bearing in mind it could contain alpha characters
                    d->keepNewNumber(transaction);
                    file->modifyTransaction(transaction);
                }
            }
            emit statusProgress(i++, 0);

            // update m_transactions to contain the newly created transaction so that
            // it is selected as the current one
            // we need to do that before we commit the transaction to the engine
            // as we need it during the update of the views that is caused by committing already.
            if (newTransactionCreated) {
                d->m_transactions.clear();
                MyMoneySplit s;
                // a transaction w/o a single split should not exist and adding it
                // should throw an exception in MyMoneyFile::addTransaction, but we
                // remain on the save side of things to check for it
                if (t.splitCount() > 0)
                    s = t.splits().front();
                KMyMoneyRegister::SelectedTransaction st(t, s, QString());
                d->m_transactions.append(st);
            }

            //    Save pricing information
            foreach (const auto split, t.splits()) {
                if ((split.action() != "Buy") &&
                        (split.action() != "Reinvest")) {
                    continue;
                }
                QString id = split.accountId();
                auto acc = file->account(id);
                MyMoneySecurity sec = file->security(acc.currencyId());
                MyMoneyPrice price(acc.currencyId(),
                                   sec.tradingCurrency(),
                                   t.postDate(),
                                   split.price(), "Transaction");
                file->addPrice(price);
                break;
            }

            ft.commit();

            // now analyze the balances and spit out warnings to the user
            QMap<QString, bool>::const_iterator it_a;

            if (!suppressBalanceWarnings) {
                for (it_a = accountIds.constBegin(); it_a != accountIds.constEnd(); ++it_a) {
                    QString msg;
                    auto acc = file->account(it_a.key());
                    MyMoneyMoney balance = file->balance(acc.id());
                    const MyMoneySecurity& sec = file->security(acc.currencyId());
                    QString key;
                    key = "minBalanceEarly";
                    if (!acc.value(key).isEmpty()) {
                        if (minBalanceEarly[acc.id()] == false && balance < MyMoneyMoney(acc.value(key))) {
                            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)));
                        }
                    }
                    key = "minBalanceAbsolute";
                    if (!acc.value(key).isEmpty()) {
                        if (minBalanceAbsolute[acc.id()] == false && balance < MyMoneyMoney(acc.value(key))) {
                            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)));
                        }
                    }
                    key = "maxCreditEarly";
                    if (!acc.value(key).isEmpty()) {
                        if (maxCreditEarly[acc.id()] == false && balance < MyMoneyMoney(acc.value(key))) {
                            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)));
                        }
                    }
                    key = "maxCreditAbsolute";
                    if (!acc.value(key).isEmpty()) {
                        if (maxCreditAbsolute[acc.id()] == false && balance < MyMoneyMoney(acc.value(key))) {
                            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)));
                        }
                    }

                    if (!msg.isEmpty()) {
                        emit balanceWarning(d->m_regForm, acc, msg);
                    }
                }
            }
        } catch (const MyMoneyException &e) {
            qDebug("Unable to store transaction within engine: %s", e.what());
        }

        emit statusProgress(-1, -1);
        emit statusMsg(QString());

    }
    return storeTransactions;
}

void TransactionEditor::resizeForm()
{
    Q_D(TransactionEditor);
    // force resizeing of the columns in the form

    if (auto form = dynamic_cast<KMyMoneyTransactionForm::TransactionForm*>(d->m_regForm))
        QMetaObject::invokeMethod(form, "resize", Qt::QueuedConnection, QGenericReturnArgument(), Q_ARG(int, (int)eWidgets::eTransactionForm::Column::Value1));
}

void TransactionEditor::slotNewPayee(const QString& newnameBase, QString& id)
{
    KMyMoneyUtils::newPayee(newnameBase, id);
}

void TransactionEditor::slotNewTag(const QString& newnameBase, QString& id)
{
    KMyMoneyUtils::newTag(newnameBase, id);
}

void TransactionEditor::slotNewCategory(MyMoneyAccount& account, const MyMoneyAccount& parent)
{
    KNewAccountDlg::newCategory(account, parent);
}

void TransactionEditor::slotNewInvestment(MyMoneyAccount& account, const MyMoneyAccount& parent)
{
    KNewInvestmentWizard::newInvestment(account, parent);
}

void TransactionEditor::setReadOnlyMode(bool readOnly)
{
    Q_D(TransactionEditor);
    d->m_readOnly = readOnly;
}
