File indexing completed on 2024-05-12 16:42:12

0001 /*
0002     SPDX-FileCopyrightText: 2009-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 "stdtransactioneditor.h"
0008 #include "transactioneditor_p.h"
0009 
0010 // ----------------------------------------------------------------------------
0011 // QT Includes
0012 
0013 #include <QLabel>
0014 #include <QApplication>
0015 #include <QList>
0016 #include <QPushButton>
0017 
0018 // ----------------------------------------------------------------------------
0019 // KDE Includes
0020 
0021 #include <KTextEdit>
0022 #include <KLocalizedString>
0023 #include <KMessageBox>
0024 #include <KStandardGuiItem>
0025 
0026 // ----------------------------------------------------------------------------
0027 // Project Includes
0028 
0029 #include "kmymoneyreconcilecombo.h"
0030 #include "kmymoneycashflowcombo.h"
0031 #include "kmymoneypayeecombo.h"
0032 #include "kmymoneytagcombo.h"
0033 #include "ktagcontainer.h"
0034 #include "tabbar.h"
0035 #include "kmymoneycategory.h"
0036 #include "kmymoneymvccombo.h"
0037 #include "kmymoneydateinput.h"
0038 #include "amountedit.h"
0039 #include "kmymoneylineedit.h"
0040 #include "kmymoneyaccountselector.h"
0041 #include "mymoneyfile.h"
0042 #include "mymoneypayee.h"
0043 #include "mymoneytag.h"
0044 #include "kmymoneyutils.h"
0045 #include "kmymoneycompletion.h"
0046 #include "transaction.h"
0047 #include "transactionform.h"
0048 #include "mymoneytransactionfilter.h"
0049 #include "kmymoneysettings.h"
0050 #include "transactioneditorcontainer.h"
0051 
0052 #include "ksplittransactiondlg.h"
0053 #include "kcurrencycalculator.h"
0054 #include "kselecttransactionsdlg.h"
0055 #include "widgetenums.h"
0056 
0057 using namespace eWidgets;
0058 using namespace KMyMoneyRegister;
0059 using namespace KMyMoneyTransactionForm;
0060 
0061 class StdTransactionEditorPrivate : public TransactionEditorPrivate
0062 {
0063     Q_DISABLE_COPY(StdTransactionEditorPrivate)
0064 
0065 public:
0066     explicit StdTransactionEditorPrivate(StdTransactionEditor *qq) :
0067         TransactionEditorPrivate(qq),
0068         m_inUpdateVat(false)
0069     {
0070     }
0071 
0072     ~StdTransactionEditorPrivate()
0073     {
0074     }
0075 
0076     MyMoneyMoney m_shares;
0077     bool         m_inUpdateVat;
0078 };
0079 
0080 StdTransactionEditor::StdTransactionEditor() :
0081     TransactionEditor(*new StdTransactionEditorPrivate(this))
0082 {
0083 }
0084 
0085 StdTransactionEditor::StdTransactionEditor(TransactionEditorContainer* regForm,
0086         KMyMoneyRegister::Transaction* item,
0087         const KMyMoneyRegister::SelectedTransactions& list,
0088         const QDate& lastPostDate) :
0089     TransactionEditor(*new StdTransactionEditorPrivate(this),
0090                       regForm,
0091                       item,
0092                       list,
0093                       lastPostDate)
0094 {
0095 }
0096 
0097 StdTransactionEditor::~StdTransactionEditor()
0098 {
0099 }
0100 
0101 void StdTransactionEditor::createEditWidgets()
0102 {
0103     Q_D(StdTransactionEditor);
0104     // we only create the account widget in case it is needed
0105     // to avoid confusion in the tab order later on.
0106     if (d->m_item->showRowInForm(0)) {
0107         auto account = new KMyMoneyCategory;
0108         account->setPlaceholderText(i18n("Account"));
0109         account->setObjectName(QLatin1String("Account"));
0110         d->m_editWidgets["account"] = account;
0111         connect(account, &QComboBox::editTextChanged, this, &StdTransactionEditor::slotUpdateButtonState);
0112         connect(account, &KMyMoneyCombo::itemSelected, this, &StdTransactionEditor::slotUpdateAccount);
0113     }
0114 
0115     auto payee = new KMyMoneyPayeeCombo;
0116     payee->setPlaceholderText(i18n("Payer/Receiver"));
0117     payee->setObjectName(QLatin1String("Payee"));
0118     d->m_editWidgets["payee"] = payee;
0119 
0120     connect(payee, &KMyMoneyMVCCombo::createItem, this, &StdTransactionEditor::slotNewPayee);
0121     connect(payee, &KMyMoneyMVCCombo::objectCreation, this, &StdTransactionEditor::objectCreation);
0122     connect(payee, &KMyMoneyMVCCombo::itemSelected, this, &StdTransactionEditor::slotUpdatePayee);
0123     connect(payee, &QComboBox::editTextChanged, this, &StdTransactionEditor::slotUpdateButtonState);
0124 
0125     auto category = new KMyMoneyCategory(true, nullptr);
0126     category->setPlaceholderText(i18n("Category/Account"));
0127     category->setObjectName(QLatin1String("Category/Account"));
0128     d->m_editWidgets["category"] = category;
0129     connect(category, &KMyMoneyCombo::itemSelected, this, &StdTransactionEditor::slotUpdateCategory);
0130     connect(category, &QComboBox::editTextChanged, this, &StdTransactionEditor::slotUpdateButtonState);
0131     connect(category, &KMyMoneyCombo::createItem, this, &StdTransactionEditor::slotCreateCategory);
0132     connect(category, &KMyMoneyCombo::objectCreation, this, &StdTransactionEditor::objectCreation);
0133     connect(category->splitButton(), &QAbstractButton::clicked, this, &StdTransactionEditor::slotEditSplits);
0134     // initially disable the split button since we don't have an account set
0135     if (category->splitButton())
0136         category->splitButton()->setDisabled(d->m_account.id().isEmpty());
0137 
0138     auto tag = new KTagContainer;
0139     tag->tagCombo()->setPlaceholderText(i18n("Tag"));
0140     tag->tagCombo()->setObjectName(QLatin1String("Tag"));
0141     d->m_editWidgets["tag"] = tag;
0142     connect(tag->tagCombo(), &KMyMoneyMVCCombo::createItem, this, &StdTransactionEditor::slotNewTag);
0143     connect(tag->tagCombo(), &KMyMoneyMVCCombo::objectCreation, this, &StdTransactionEditor::objectCreation);
0144 
0145     auto memo = new KTextEdit;
0146     memo->setObjectName(QLatin1String("Memo"));
0147     memo->setTabChangesFocus(true);
0148     connect(memo, &QTextEdit::textChanged, this, &StdTransactionEditor::slotUpdateMemoState);
0149     connect(memo, &QTextEdit::textChanged, this, &StdTransactionEditor::slotUpdateButtonState);
0150     d->m_editWidgets["memo"] = memo;
0151     d->m_memoText.clear();
0152     d->m_memoChanged = false;
0153 
0154     bool showNumberField = true;
0155     switch (d->m_account.accountType()) {
0156     case eMyMoney::Account::Type::Savings:
0157     case eMyMoney::Account::Type::Cash:
0158     case eMyMoney::Account::Type::Loan:
0159     case eMyMoney::Account::Type::AssetLoan:
0160     case eMyMoney::Account::Type::Asset:
0161     case eMyMoney::Account::Type::Liability:
0162     case eMyMoney::Account::Type::Equity:
0163         showNumberField = KMyMoneySettings::alwaysShowNrField();
0164         break;
0165 
0166     case eMyMoney::Account::Type::Income:
0167     case eMyMoney::Account::Type::Expense:
0168         showNumberField = false;
0169         break;
0170 
0171     default:
0172         break;
0173     }
0174 
0175     if (showNumberField) {
0176         auto number = new KMyMoneyLineEdit;
0177         number->setPlaceholderText(i18n("Number"));
0178         number->setObjectName(QLatin1String("Number"));
0179         d->m_editWidgets["number"] = number;
0180         connect(number, &KMyMoneyLineEdit::lineChanged, this, &StdTransactionEditor::slotNumberChanged);
0181         // number->installEventFilter(this);
0182     }
0183 
0184     auto postDate = new KMyMoneyDateInput;
0185     d->m_editWidgets["postdate"] = postDate;
0186     postDate->setObjectName(QLatin1String("PostDate"));
0187     connect(postDate, &KMyMoneyDateInput::dateChanged, this, &StdTransactionEditor::slotUpdateButtonState);
0188     postDate->setDate(QDate());
0189 
0190     auto value = new AmountEdit;
0191     d->m_editWidgets["amount"] = value;
0192     value->setObjectName(QLatin1String("Amount"));
0193     value->setCalculatorButtonVisible(true);
0194     connect(value, &AmountEdit::valueChanged, this, &StdTransactionEditor::slotUpdateAmount);
0195     connect(value, &AmountEdit::textChanged, this, &StdTransactionEditor::slotUpdateButtonState);
0196 
0197     value = new AmountEdit;
0198     d->m_editWidgets["payment"] = value;
0199     value->setObjectName(QLatin1String("Payment"));
0200     value->setCalculatorButtonVisible(true);
0201     connect(value, &AmountEdit::valueChanged, this, &StdTransactionEditor::slotUpdatePayment);
0202     connect(value, &AmountEdit::textChanged, this, &StdTransactionEditor::slotUpdateButtonState);
0203 
0204     value = new AmountEdit;
0205     d->m_editWidgets["deposit"] = value;
0206     value->setObjectName(QLatin1String("Deposit"));
0207     value->setCalculatorButtonVisible(true);
0208     connect(value, &AmountEdit::valueChanged, this, &StdTransactionEditor::slotUpdateDeposit);
0209     connect(value, &AmountEdit::textChanged, this, &StdTransactionEditor::slotUpdateButtonState);
0210 
0211     auto cashflow = new KMyMoneyCashFlowCombo(d->m_account.accountGroup(), nullptr);
0212     d->m_editWidgets["cashflow"] = cashflow;
0213     cashflow->setObjectName(QLatin1String("Cashflow"));
0214     connect(cashflow, &KMyMoneyCashFlowCombo::directionSelected, this, &StdTransactionEditor::slotUpdateCashFlow);
0215 
0216     auto reconcile = new KMyMoneyReconcileCombo;
0217     d->m_editWidgets["status"] = reconcile;
0218     reconcile->setObjectName(QLatin1String("Reconcile"));
0219 
0220     KMyMoneyRegister::QWidgetContainer::iterator it_w;
0221     for (it_w = d->m_editWidgets.begin(); it_w != d->m_editWidgets.end(); ++it_w) {
0222         (*it_w)->installEventFilter(this);
0223     }
0224     // if we don't have more than 1 selected transaction, we don't need
0225     // the "don't change" item in some of the combo widgets
0226     if (!isMultiSelection()) {
0227         reconcile->removeDontCare();
0228         cashflow->removeDontCare();
0229     }
0230 
0231     QLabel* label;
0232     d->m_editWidgets["account-label"] = label = new QLabel(i18n("Account"));
0233     label->setAlignment(Qt::AlignVCenter);
0234 
0235     d->m_editWidgets["category-label"] = label = new QLabel(i18n("Category"));
0236     label->setAlignment(Qt::AlignVCenter);
0237 
0238     d->m_editWidgets["tag-label"] = label = new QLabel(i18n("Tags"));
0239     label->setAlignment(Qt::AlignVCenter);
0240 
0241     d->m_editWidgets["memo-label"] = label = new QLabel(i18n("Memo"));
0242     label->setAlignment(Qt::AlignVCenter);
0243 
0244     d->m_editWidgets["number-label"] = label = new QLabel(i18n("Number"));
0245     label->setAlignment(Qt::AlignVCenter);
0246 
0247     d->m_editWidgets["date-label"] = label = new QLabel(i18n("Date"));
0248     label->setAlignment(Qt::AlignVCenter);
0249 
0250     d->m_editWidgets["amount-label"] = label = new QLabel(i18n("Amount"));
0251     label->setAlignment(Qt::AlignVCenter);
0252 
0253     d->m_editWidgets["status-label"] = label = new QLabel(i18n("Status"));
0254     label->setAlignment(Qt::AlignVCenter);
0255 
0256     // create a copy of tabbar above the form (if we are created for a form)
0257     auto form = dynamic_cast<KMyMoneyTransactionForm::TransactionForm*>(d->m_regForm);
0258     if (form) {
0259         auto tabbar = new KMyMoneyTransactionForm::TabBar;
0260         d->m_editWidgets["tabbar"] = tabbar;
0261         tabbar->setObjectName(QLatin1String("TabBar"));
0262         tabbar->copyTabs(form->getTabBar());
0263         connect(tabbar, &KMyMoneyTransactionForm::TabBar::tabCurrentChanged, this, &StdTransactionEditor::slotUpdateAction);
0264         connect(tabbar, &KMyMoneyTransactionForm::TabBar::tabCurrentChanged, this, &TransactionEditor::operationTypeChanged);
0265     }
0266 
0267     setupPrecision();
0268 }
0269 
0270 void StdTransactionEditor::setupCategoryWidget(QString& categoryId)
0271 {
0272     Q_D(StdTransactionEditor);
0273     if (auto categoryWidget = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"]))
0274         TransactionEditor::setupCategoryWidget(categoryWidget, d->m_splits, categoryId, SLOT(slotEditSplits()));
0275 
0276     if (d->m_splits.count() == 1)
0277         d->m_shares = d->m_splits[0].shares();
0278 }
0279 
0280 bool StdTransactionEditor::isTransfer(const QString& accId1, const QString& accId2) const
0281 {
0282     if (accId1.isEmpty() || accId2.isEmpty())
0283         return false;
0284 
0285     return MyMoneyFile::instance()->account(accId1).isIncomeExpense() == MyMoneyFile::instance()->account(accId2).isIncomeExpense();
0286 }
0287 
0288 void StdTransactionEditor::loadEditWidgets(eRegister::Action action)
0289 {
0290     Q_D(StdTransactionEditor);
0291     // don't kick off VAT processing from here
0292     d->m_inUpdateVat = true;
0293 
0294     QWidget* w;
0295     AccountSet aSet;
0296 
0297     // load the account widget
0298     if (auto account = dynamic_cast<KMyMoneyCategory*>(haveWidget("account"))) {
0299         aSet.addAccountGroup(eMyMoney::Account::Type::Asset);
0300         aSet.addAccountGroup(eMyMoney::Account::Type::Liability);
0301         aSet.removeAccountType(eMyMoney::Account::Type::AssetLoan);
0302         aSet.removeAccountType(eMyMoney::Account::Type::CertificateDep);
0303         aSet.removeAccountType(eMyMoney::Account::Type::Investment);
0304         aSet.removeAccountType(eMyMoney::Account::Type::Stock);
0305         aSet.removeAccountType(eMyMoney::Account::Type::MoneyMarket);
0306         aSet.removeAccountType(eMyMoney::Account::Type::Loan);
0307         aSet.load(account->selector());
0308         account->completion()->setSelected(d->m_account.id());
0309         account->slotItemSelected(d->m_account.id());
0310     }
0311 
0312     // load the payee widget
0313     auto payee = dynamic_cast<KMyMoneyPayeeCombo*>(d->m_editWidgets["payee"]);
0314     if (payee)
0315         payee->loadPayees(MyMoneyFile::instance()->payeeList());
0316 
0317     // load the category widget
0318     auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"]);
0319 
0320     if (category)
0321         disconnect(category, &KMyMoneyCategory::focusIn, this, &StdTransactionEditor::slotEditSplits);
0322 
0323     // load the tag widget
0324     //auto tag = dynamic_cast<KMyMoneyTagCombo*>(m_editWidgets["tag"]);
0325     auto tag = dynamic_cast<KTagContainer*>(d->m_editWidgets["tag"]);
0326     if (tag)
0327         tag->loadTags(MyMoneyFile::instance()->tagList());
0328 
0329     // check if the current transaction has a reference to an equity account
0330     auto haveEquityAccount = false;
0331     foreach (const auto split, d->m_transaction.splits()) {
0332         auto acc = MyMoneyFile::instance()->account(split.accountId());
0333         if (acc.accountType() == eMyMoney::Account::Type::Equity) {
0334             haveEquityAccount = true;
0335             break;
0336         }
0337     }
0338 
0339     aSet.clear();
0340     aSet.addAccountGroup(eMyMoney::Account::Type::Asset);
0341     aSet.addAccountGroup(eMyMoney::Account::Type::Liability);
0342     aSet.addAccountGroup(eMyMoney::Account::Type::Income);
0343     aSet.addAccountGroup(eMyMoney::Account::Type::Expense);
0344     if (KMyMoneySettings::expertMode() || haveEquityAccount)
0345         aSet.addAccountGroup(eMyMoney::Account::Type::Equity);
0346 
0347     aSet.removeAccountType(eMyMoney::Account::Type::CertificateDep);
0348     aSet.removeAccountType(eMyMoney::Account::Type::Investment);
0349     aSet.removeAccountType(eMyMoney::Account::Type::Stock);
0350     aSet.removeAccountType(eMyMoney::Account::Type::MoneyMarket);
0351     if (category)
0352         aSet.load(category->selector());
0353 
0354     // if an account is specified then remove it from the widget so that the user
0355     // cannot create a transfer with from and to account being the same account
0356     if (category && !d->m_account.id().isEmpty())
0357         category->selector()->removeItem(d->m_account.id());
0358 
0359     //  also show memo text if isMultiSelection()
0360     if (auto memoWidget = dynamic_cast<KTextEdit*>(d->m_editWidgets["memo"]))
0361         memoWidget->setText(d->m_split.memo());
0362     // need to know if it changed
0363     d->m_memoText = d->m_split.memo();
0364     d->m_memoChanged = false;
0365 
0366     if (!isMultiSelection()) {
0367         if (auto dateWidget = dynamic_cast<KMyMoneyDateInput*>(d->m_editWidgets["postdate"])) {
0368             if (d->m_transaction.postDate().isValid())
0369                 dateWidget->setDate(d->m_transaction.postDate());
0370             else if (d->m_lastPostDate.isValid())
0371                 dateWidget->setDate(d->m_lastPostDate);
0372             else
0373                 dateWidget->setDate(QDate::currentDate());
0374         }
0375 
0376         if ((w = haveWidget("number")) != 0) {
0377             if (auto lineEdit = dynamic_cast<KMyMoneyLineEdit*>(w))
0378                 lineEdit->loadText(d->m_split.number());
0379             if (d->m_transaction.id().isEmpty()                              // new transaction
0380                     && dynamic_cast<KMyMoneyLineEdit*>(w)->text().isEmpty()   // no number filled in
0381                     && d->m_account.accountType() == eMyMoney::Account::Type::Checkings   // checkings account
0382                     && KMyMoneySettings::autoIncCheckNumber()           // and auto inc number turned on?
0383                     && action != eRegister::Action::Deposit              // only transfers or withdrawals
0384                     && d->m_paymentMethod == eMyMoney::Schedule::PaymentType::WriteChecque) {// only for WriteChecque
0385                 assignNextNumber();
0386             }
0387         }
0388         if (auto statusWidget = dynamic_cast<KMyMoneyReconcileCombo*>(d->m_editWidgets["status"]))
0389             statusWidget->setState(d->m_split.reconcileFlag());
0390 
0391         QString payeeId = d->m_split.payeeId();
0392         if (payee && !payeeId.isEmpty())
0393             payee->setSelectedItem(payeeId);
0394 
0395         QList<QString> t = d->m_split.tagIdList();
0396         if (tag && !t.isEmpty())
0397             for (auto i = 0; i < t.size(); ++i)
0398                 tag->addTagWidget(t[i]);
0399 
0400         d->m_splits.clear();
0401         if (d->m_transaction.splitCount() < 2) {
0402             if (category && category->completion()) {
0403                 category->completion()->setSelected(QString());
0404             }
0405         } else {
0406             foreach (const auto split, d->m_transaction.splits()) {
0407                 if (split == d->m_split)
0408                     continue;
0409                 d->m_splits.append(split);
0410             }
0411         }
0412         QString categoryId;
0413         setupCategoryWidget(categoryId);
0414 
0415         if ((w = haveWidget("cashflow")) != 0) {
0416             if (auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(w))
0417                 cashflow->setDirection(!d->m_split.value().isPositive() ? eRegister::CashFlowDirection::Payment : eRegister::CashFlowDirection::Deposit);  //  include isZero case
0418         }
0419 
0420         if ((w = haveWidget("category-label")) != 0) {
0421             if (auto categoryLabel = dynamic_cast<QLabel*>(w)) {
0422                 if (isTransfer(d->m_split.accountId(), categoryId)) {
0423                     if (d->m_split.value().isPositive())
0424                         categoryLabel->setText(i18n("Transfer from"));
0425                     else
0426                         categoryLabel->setText(i18n("Transfer to"));
0427                 }
0428             }
0429         }
0430 
0431         MyMoneyMoney value = d->m_split.shares();
0432 
0433         if (haveWidget("deposit")) {
0434             auto depositWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["deposit"]);
0435             auto paymentWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["payment"]);
0436             if (depositWidget && paymentWidget) {
0437                 if (d->m_split.shares().isNegative()) {
0438                     depositWidget->setText(QString());
0439                     paymentWidget->setValue(value.abs());
0440                 } else {
0441                     depositWidget->setValue(value.abs());
0442                     paymentWidget->setText(QString());
0443                 }
0444             }
0445         }
0446         if ((w = haveWidget("amount")) != 0) {
0447             if (auto amountWidget = dynamic_cast<AmountEdit*>(w))
0448                 amountWidget->setValue(value.abs());
0449         }
0450 
0451         // try to preset for specific action if a new transaction is being started
0452         if (d->m_transaction.id().isEmpty()) {
0453             if ((w = haveWidget("category-label")) != 0) {
0454                 auto tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(haveWidget("tabbar"));
0455                 if (action == eRegister::Action::None) {
0456                     if (tabbar) {
0457                         action = static_cast<eRegister::Action>(tabbar->currentIndex());
0458                     }
0459                 }
0460                 if (action != eRegister::Action::None) {
0461                     if (auto categoryLabel = dynamic_cast<QLabel*>(w)) {
0462                         if (action == eRegister::Action::Transfer) {
0463                             if (d->m_split.value().isPositive())
0464                                 categoryLabel->setText(i18n("Transfer from"));
0465                             else
0466                                 categoryLabel->setText(i18n("Transfer to"));
0467                         }
0468                     }
0469                     if ((w = haveWidget("cashflow")) != 0) {
0470                         if (auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(w)) {
0471                             if (action == eRegister::Action::Deposit || (action == eRegister::Action::Transfer && d->m_split.value().isPositive()))
0472                                 cashflow->setDirection(eRegister::CashFlowDirection::Deposit);
0473                             else
0474                                 cashflow->setDirection(eRegister::CashFlowDirection::Payment);
0475                         }
0476                     }
0477                     if (tabbar) {
0478                         tabbar->setCurrentIndex((int)action);
0479                     }
0480                 }
0481             }
0482         } else {
0483             if (auto tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(haveWidget("tabbar"))) {
0484                 if (!isTransfer(d->m_split.accountId(), categoryId))
0485                     tabbar->setCurrentIndex(d->m_split.value().isNegative() ? (int)eRegister::Action::Withdrawal : (int)eRegister::Action::Deposit);
0486                 else
0487                     tabbar->setCurrentIndex((int)eRegister::Action::Transfer);
0488             }
0489         }
0490 
0491     } else {  //  isMultiSelection()
0492         if (auto postDateWidget = dynamic_cast<KMyMoneyDateInput*>(d->m_editWidgets["postdate"]))
0493             postDateWidget->loadDate(QDate());
0494         if (auto statusWidget = dynamic_cast<KMyMoneyReconcileCombo*>(d->m_editWidgets["status"]))
0495             statusWidget->setState(eMyMoney::Split::State::Unknown);
0496         if (haveWidget("deposit")) {
0497             if (auto depositWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["deposit"])) {
0498                 depositWidget->setText(QString());
0499                 depositWidget->setAllowEmpty();
0500             }
0501             if (auto paymentWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["payment"])) {
0502                 paymentWidget->setText(QString());
0503                 paymentWidget->setAllowEmpty();
0504             }
0505         }
0506         if ((w = haveWidget("amount")) != 0) {
0507             if (auto amountWidget = dynamic_cast<AmountEdit*>(w)) {
0508                 amountWidget->setText(QString());
0509                 amountWidget->setAllowEmpty();
0510             }
0511         }
0512 
0513         slotUpdateAction((int)action);
0514 
0515         if ((w = haveWidget("tabbar")) != 0) {
0516             w->setEnabled(false);
0517         }
0518 
0519         if (category && category->completion())
0520             category->completion()->setSelected(QString());
0521     }
0522 
0523     // allow kick off VAT processing again
0524     d->m_inUpdateVat = false;
0525 }
0526 
0527 void StdTransactionEditor::loadEditWidgets()
0528 {
0529     loadEditWidgets(eRegister::Action::None);
0530 }
0531 
0532 QWidget* StdTransactionEditor::firstWidget() const
0533 {
0534     Q_D(const StdTransactionEditor);
0535     QWidget* w = nullptr;
0536     if (d->m_initialAction != eRegister::Action::None) {
0537         w = haveWidget("payee");
0538     }
0539     return w;
0540 }
0541 
0542 void StdTransactionEditor::slotReloadEditWidgets()
0543 {
0544     Q_D(StdTransactionEditor);
0545     // reload category widget
0546     if (auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"])) {
0547         QString categoryId = category->selectedItem();
0548 
0549         AccountSet aSet;
0550         aSet.addAccountGroup(eMyMoney::Account::Type::Asset);
0551         aSet.addAccountGroup(eMyMoney::Account::Type::Liability);
0552         aSet.addAccountGroup(eMyMoney::Account::Type::Income);
0553         aSet.addAccountGroup(eMyMoney::Account::Type::Expense);
0554         if (KMyMoneySettings::expertMode())
0555             aSet.addAccountGroup(eMyMoney::Account::Type::Equity);
0556         aSet.load(category->selector());
0557 
0558         // if an account is specified then remove it from the widget so that the user
0559         // cannot create a transfer with from and to account being the same account
0560         if (!d->m_account.id().isEmpty())
0561             category->selector()->removeItem(d->m_account.id());
0562 
0563         if (!categoryId.isEmpty())
0564             category->setSelectedItem(categoryId);
0565     }
0566 
0567 
0568     // reload payee widget
0569     if (auto payee = dynamic_cast<KMyMoneyPayeeCombo*>(d->m_editWidgets["payee"])) {
0570         QString payeeId = payee->selectedItem();
0571 
0572         payee->loadPayees(MyMoneyFile::instance()->payeeList());
0573 
0574         if (!payeeId.isEmpty()) {
0575             payee->setSelectedItem(payeeId);
0576         }
0577     }
0578 
0579     // reload tag widget
0580     if (auto tag = dynamic_cast<KTagContainer*>(d->m_editWidgets["tag"])) {
0581         QString tagId = tag->tagCombo()->selectedItem();
0582 
0583         tag->loadTags(MyMoneyFile::instance()->tagList());
0584 
0585         if (!tagId.isEmpty()) {
0586             tag->RemoveAllTagWidgets();
0587             tag->addTagWidget(tagId);
0588         }
0589     }
0590 }
0591 
0592 void StdTransactionEditor::slotUpdatePayee(const QString& payeeId)
0593 {
0594     // in case of an empty payee, there is nothing to do
0595     if (payeeId.isEmpty())
0596         return;
0597 
0598     Q_D(StdTransactionEditor);
0599     // we have a new payee assigned to this transaction.
0600     // in case there is no category assigned, no value entered and no
0601     // memo available, we search for the last transaction of this payee
0602     // in the account.
0603     if (d->m_transaction.id().isEmpty() //
0604             && d->m_splits.count() == 0 //
0605             && KMyMoneySettings::autoFillTransaction() != 0) {
0606         // check if category is empty
0607         if (auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"])) {
0608             QStringList list;
0609             category->selectedItems(list);
0610             if (!list.isEmpty())
0611                 return;
0612         }
0613 
0614         // check if memo is empty
0615         auto memo = dynamic_cast<KTextEdit*>(d->m_editWidgets["memo"]);
0616         if (memo && !memo->toPlainText().isEmpty())
0617             return;
0618 
0619         // check if all value fields are empty
0620         QStringList fields{"amount", "payment", "deposit"};
0621         QStringList::const_iterator it_f;
0622         for (it_f = fields.constBegin(); it_f != fields.constEnd(); ++it_f) {
0623             const auto amount = dynamic_cast<AmountEdit*>(haveWidget(*it_f));
0624             if (amount && !amount->value().isZero())
0625                 return;
0626         }
0627 
0628 #if 0
0629         // Tony mentioned, that autofill does not work when he changed the date. Well,
0630         // that certainly makes sense when you enter transactions in register mode as
0631         // opposed to form mode, because the date field is located prior to the date
0632         // field in the tab order of the widgets and the user might have already
0633         // changed it.
0634         //
0635         // So I commented out the code that checks the date but left it in for reference.
0636         // (ipwizard, 2008-04-07)
0637 
0638         // check if date has been altered by user
0639         auto postDate = dynamic_cast<KMyMoneyDateInput*>(m_editWidgets["postdate"]);
0640         if (postDate && (m_lastPostDate.isValid() && (postDate->date() != m_lastPostDate))
0641                 || (!m_lastPostDate.isValid() && (postDate->date() != QDate::currentDate())))
0642             return;
0643 #endif
0644 
0645         // if we got here, we have to autofill
0646         autoFill(payeeId);
0647     }
0648 
0649     // If payee has associated default account (category), set that now.
0650     const MyMoneyPayee& payeeObj = MyMoneyFile::instance()->payee(payeeId);
0651     if (!payeeObj.defaultAccountId().isEmpty()) {
0652         if (auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"]))
0653             category->slotItemSelected(payeeObj.defaultAccountId());
0654     }
0655 }
0656 
0657 MyMoneyMoney StdTransactionEditor::shares(const MyMoneyTransaction& t) const
0658 {
0659     Q_D(const StdTransactionEditor);
0660     MyMoneyMoney result;
0661     foreach (const auto split, t.splits()) {
0662         if (split.accountId() == d->m_account.id()) {
0663             result += split.shares();
0664         }
0665     }
0666     return result;
0667 }
0668 
0669 struct uniqTransaction {
0670     const MyMoneyTransaction* t;
0671     int   cnt;
0672 };
0673 
0674 void StdTransactionEditor::autoFill(const QString& payeeId)
0675 {
0676     Q_D(StdTransactionEditor);
0677     QList<QPair<MyMoneyTransaction, MyMoneySplit> >  list;
0678     MyMoneyTransactionFilter filter(d->m_account.id());
0679     filter.addPayee(payeeId);
0680     MyMoneyFile::instance()->transactionList(list, filter);
0681     if (!list.empty()) {
0682         // ok, we found at least one previous transaction. now we clear out
0683         // what we have collected so far and add those splits from
0684         // the previous transaction.
0685         QList<QPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator  it_t;
0686         QMap<QString, struct uniqTransaction> uniqList;
0687 
0688         // collect the transactions and see if we have any duplicates
0689         for (it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) {
0690             QString key = (*it_t).first.accountSignature();
0691             int cnt = 0;
0692             QMap<QString, struct uniqTransaction>::iterator it_u;
0693             do {
0694                 QString ukey = QString("%1-%2").arg(key).arg(cnt);
0695                 it_u = uniqList.find(ukey);
0696                 if (it_u == uniqList.end()) {
0697                     uniqList[ukey].t = &((*it_t).first);
0698                     uniqList[ukey].cnt = 1;
0699                 } else if (KMyMoneySettings::autoFillTransaction() == 1) {
0700                     // we already have a transaction with this signature. we must
0701                     // now check, if we should really treat it as a duplicate according
0702                     // to the value comparison delta.
0703                     MyMoneyMoney s1 = shares(*((*it_u).t));
0704                     MyMoneyMoney s2 = shares((*it_t).first);
0705                     if (s2.abs() > s1.abs()) {
0706                         MyMoneyMoney t(s1);
0707                         s1 = s2;
0708                         s2 = t;
0709                     }
0710                     MyMoneyMoney diff;
0711                     if (s2.isZero())
0712                         diff = s1.abs();
0713                     else
0714                         diff = ((s1 - s2) / s2).convert(10000);
0715                     if (diff.isPositive() && diff <= MyMoneyMoney(KMyMoneySettings::autoFillDifference(), 100)) {
0716                         uniqList[ukey].t = &((*it_t).first);
0717                         break;    // end while loop
0718                     }
0719                 } else if (KMyMoneySettings::autoFillTransaction() == 2) {
0720                     uniqList[ukey].t = &((*it_t).first);
0721                     (*it_u).cnt++;
0722                     break;      // end while loop
0723                 }
0724                 ++cnt;
0725             } while (it_u != uniqList.end());
0726 
0727         }
0728 
0729         MyMoneyTransaction t;
0730         if (KMyMoneySettings::autoFillTransaction() != 2) {
0731 #if 0
0732             // I removed this code to allow cancellation of an autofill if
0733             // it does not match even if there is only a single matching
0734             // transaction for the payee in question. In case, we want to revert
0735             // to the old behavior, don't forget to uncomment the closing
0736             // brace further down in the code as well. (ipwizard 2009-01-16)
0737             if (uniqList.count() == 1) {
0738                 t = list.last().first;
0739             } else {
0740 #endif
0741                 QPointer<KSelectTransactionsDlg> dlg = new KSelectTransactionsDlg(d->m_account, d->m_regForm);
0742                 dlg->setWindowTitle(i18n("Select autofill transaction"));
0743 
0744                 QMap<QString, struct uniqTransaction>::const_iterator it_u;
0745                 for (it_u = uniqList.constBegin(); it_u != uniqList.constEnd(); ++it_u) {
0746                     dlg->addTransaction(*(*it_u).t);
0747                 }
0748 
0749                 auto tRegister = dlg->getRegister();
0750                 // setup sort order
0751                 tRegister->setSortOrder("1,-9,-4");
0752                 // sort the transactions according to the sort setting
0753                 tRegister->sortItems();
0754 
0755                 // and select the last item
0756                 if (tRegister->lastItem())
0757                     tRegister->selectItem(tRegister->lastItem());
0758 
0759                 if (dlg->exec() == QDialog::Accepted) {
0760                     t = dlg->transaction();
0761                 }
0762 #if 0
0763             }
0764 #endif
0765         } else {
0766             int maxCnt = 0;
0767             QMap<QString, struct uniqTransaction>::const_iterator it_u;
0768             for (it_u = uniqList.constBegin(); it_u != uniqList.constEnd(); ++it_u) {
0769                 if ((*it_u).cnt > maxCnt) {
0770                     t = *(*it_u).t;
0771                     maxCnt = (*it_u).cnt;
0772                 }
0773             }
0774         }
0775 
0776         if (t != MyMoneyTransaction()) {
0777             d->m_transaction.removeSplits();
0778             d->m_split = MyMoneySplit();
0779             MyMoneySplit otherSplit;
0780             foreach (const auto split, t.splits()) {
0781                 MyMoneySplit s(split);
0782                 s.setReconcileFlag(eMyMoney::Split::State::NotReconciled);
0783                 s.setReconcileDate(QDate());
0784                 s.clearId();
0785                 s.setBankID(QString());
0786                 // older versions of KMyMoney used to set the action
0787                 // we don't need this anymore
0788                 if (s.action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization)
0789                         && s.action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Interest))  {
0790                     s.setAction(QString());
0791                 }
0792 
0793                 // FIXME update check number. The old comment contained
0794                 //
0795                 // <quote>
0796                 // If a check number is already specified by the user it is
0797                 // used. If the input field is empty and the previous transaction
0798                 // contains a checknumber, the next usable check number will be assigned
0799                 // to the transaction.
0800                 // </quote>
0801 
0802                 auto editNr = dynamic_cast<KMyMoneyLineEdit*>(haveWidget("number"));
0803                 if (editNr && !editNr->text().isEmpty()) {
0804                     s.setNumber(editNr->text());
0805                 } else if (!s.number().isEmpty()) {
0806                     s.setNumber(KMyMoneyUtils::nextFreeCheckNumber(d->m_account));
0807                 }
0808 
0809                 // if the memos should not be used with autofill or
0810                 // if the transaction has exactly two splits, remove
0811                 // the memo text of the split that does not reference
0812                 // the current account. This allows the user to change
0813                 // the autofilled memo text which will then also be used
0814                 // in this split. See createTransaction() for this logic.
0815                 if ((s.accountId() != d->m_account.id() && t.splitCount() == 2) || !KMyMoneySettings::autoFillUseMemos())
0816                     s.setMemo(QString());
0817 
0818                 d->m_transaction.addSplit(s);
0819                 if (s.accountId() == d->m_account.id() && d->m_split == MyMoneySplit()) {
0820                     d->m_split = s;
0821                 } else {
0822                     otherSplit = s;
0823                 }
0824             }
0825 
0826             // make sure to extract the right action
0827             eRegister::Action action;
0828             action = d->m_split.shares().isNegative() ? eRegister::Action::Withdrawal : eRegister::Action::Deposit;
0829 
0830             if (d->m_transaction.splitCount() == 2) {
0831                 auto acc = MyMoneyFile::instance()->account(otherSplit.accountId());
0832                 if (acc.isAssetLiability())
0833                     action = eRegister::Action::Transfer;
0834             }
0835 
0836             // now setup the widgets with the new data but keep the date
0837             if (auto postdateWidget = dynamic_cast<KMyMoneyDateInput*>(d->m_editWidgets["postdate"])) {
0838                 auto date = postdateWidget->date();
0839                 loadEditWidgets(action);
0840                 postdateWidget->setDate(date);
0841             }
0842         }
0843     }
0844 
0845     // focus jumps into the category field
0846     QWidget* w;
0847     if ((w = haveWidget("payee")) != 0) {
0848         w->setFocus();
0849     }
0850 }
0851 
0852 void StdTransactionEditor::slotUpdateAction(int action)
0853 {
0854     Q_D(StdTransactionEditor);
0855     auto tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(haveWidget("tabbar"));
0856     if (tabbar) {
0857         auto categoryLabel = dynamic_cast<QLabel*>(haveWidget("category-label"));
0858         auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(d->m_editWidgets["cashflow"]);
0859         if (!categoryLabel || !cashflow)
0860             return;
0861         switch (action) {
0862         case (int)eRegister::Action::Deposit:
0863             categoryLabel->setText(i18n("Category"));
0864             cashflow->setDirection(eRegister::CashFlowDirection::Deposit);
0865             break;
0866         case (int)eRegister::Action::Transfer:
0867             if (d->m_split.shares().isNegative()) {
0868                 cashflow->setDirection(eRegister::CashFlowDirection::Payment);
0869                 categoryLabel->setText(i18n("Transfer to"));
0870             } else {
0871                 cashflow->setDirection(eRegister::CashFlowDirection::Deposit);
0872                 categoryLabel->setText(i18n("Transfer from"));
0873             }
0874             tabbar->setCurrentIndex((int)eRegister::Action::Transfer);
0875             slotUpdateCashFlow(cashflow->direction());
0876             break;
0877         case (int)eRegister::Action::Withdrawal:
0878             categoryLabel->setText(i18n("Category"));
0879             cashflow->setDirection(eRegister::CashFlowDirection::Payment);
0880             break;
0881         }
0882         resizeForm();
0883     }
0884 }
0885 
0886 void StdTransactionEditor::slotUpdateCashFlow(eRegister::CashFlowDirection dir)
0887 {
0888     auto categoryLabel = dynamic_cast<QLabel*>(haveWidget("category-label"));
0889     if (auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(haveWidget("cashflow")))
0890         cashflow->setDirection(dir);
0891     // qDebug("Update cashflow to %d", dir);
0892     if (categoryLabel) {
0893         auto tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(haveWidget("tabbar"));
0894         if (!tabbar) return;  //  no transaction form
0895         if (categoryLabel->text() != i18n("Category")) {
0896             tabbar->setCurrentIndex((int)eRegister::Action::Transfer);
0897             if (dir == eRegister::CashFlowDirection::Deposit) {
0898                 categoryLabel->setText(i18n("Transfer from"));
0899             } else {
0900                 categoryLabel->setText(i18n("Transfer to"));
0901             }
0902             resizeForm();
0903         } else {
0904             if (dir == eRegister::CashFlowDirection::Deposit)
0905                 tabbar->setCurrentIndex((int)eRegister::Action::Deposit);
0906             else
0907                 tabbar->setCurrentIndex((int)eRegister::Action::Withdrawal);
0908         }
0909     }
0910 }
0911 
0912 void StdTransactionEditor::slotUpdateCategory(const QString& id)
0913 {
0914     Q_D(StdTransactionEditor);
0915     auto categoryLabel = dynamic_cast<QLabel*>(haveWidget("category-label"));
0916     // qDebug("Update category to %s", qPrintable(id));
0917 
0918     if (categoryLabel) {
0919         auto tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(haveWidget("tabbar"));
0920         auto amount = dynamic_cast<AmountEdit*>(d->m_editWidgets["amount"]);
0921         auto val = amount ? amount->value() : MyMoneyMoney();
0922 
0923         if (categoryLabel->text() == i18n("Transfer from")) {
0924             val = -val;
0925         } else {
0926             val = val.abs();
0927         }
0928 
0929         if (tabbar) {
0930             tabbar->setTabEnabled((int)eRegister::Action::Transfer, true);
0931             tabbar->setTabEnabled((int)eRegister::Action::Deposit, true);
0932             tabbar->setTabEnabled((int)eRegister::Action::Withdrawal, true);
0933         }
0934 
0935         bool disableTransferTab = false;
0936         if (!id.isEmpty()) {
0937             auto acc = MyMoneyFile::instance()->account(id);
0938             if (acc.isAssetLiability()
0939                     || acc.accountGroup() == eMyMoney::Account::Type::Equity) {
0940                 if (tabbar) {
0941                     tabbar->setCurrentIndex((int)eRegister::Action::Transfer);
0942                     tabbar->setTabEnabled((int)eRegister::Action::Deposit, false);
0943                     tabbar->setTabEnabled((int)eRegister::Action::Withdrawal, false);
0944                 }
0945                 auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(d->m_editWidgets["cashflow"]);
0946                 if (val.isZero()) {
0947                     if (cashflow && (cashflow->direction() == eRegister::CashFlowDirection::Deposit)) {
0948                         categoryLabel->setText(i18n("Transfer from"));
0949                     } else {
0950                         categoryLabel->setText(i18n("Transfer to"));
0951                     }
0952                 } else if (val.isNegative()) {
0953                     categoryLabel->setText(i18n("Transfer from"));
0954                     if (cashflow)
0955                         cashflow->setDirection(eRegister::CashFlowDirection::Deposit);
0956                 } else
0957                     categoryLabel->setText(i18n("Transfer to"));
0958             } else {
0959                 disableTransferTab = true;
0960                 categoryLabel->setText(i18n("Category"));
0961             }
0962             updateAmount(val);
0963         } else {  //id.isEmpty()
0964             if (auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"]))
0965                 disableTransferTab = !category->currentText().isEmpty();
0966             categoryLabel->setText(i18n("Category"));
0967         }
0968         if (tabbar) {
0969             if (disableTransferTab) {
0970                 // set the proper tab before disabling the currently active tab
0971                 if (tabbar->currentIndex() == (int)eRegister::Action::Transfer) {
0972                     tabbar->setCurrentIndex(val.isPositive() ? (int)eRegister::Action::Withdrawal : (int)eRegister::Action::Deposit);
0973                 }
0974                 tabbar->setTabEnabled((int)eRegister::Action::Transfer, false);
0975             }
0976             tabbar->update();
0977         }
0978 
0979         resizeForm();
0980     }
0981     updateVAT(false);
0982 }
0983 
0984 void StdTransactionEditor::slotUpdatePayment(const QString& txt)
0985 {
0986     Q_D(StdTransactionEditor);
0987     MyMoneyMoney val(txt);
0988 
0989     auto depositWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["deposit"]);
0990     auto paymentWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["payment"]);
0991     if (!depositWidget || !paymentWidget)
0992         return;
0993 
0994     if (val.isNegative()) {
0995         depositWidget->setValue(val.abs());
0996         paymentWidget->setText(QString());
0997     } else {
0998         depositWidget->setText(QString());
0999     }
1000     updateVAT();
1001 }
1002 
1003 void StdTransactionEditor::slotUpdateDeposit(const QString& txt)
1004 {
1005     Q_D(StdTransactionEditor);
1006     MyMoneyMoney val(txt);
1007 
1008     auto depositWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["deposit"]);
1009     auto paymentWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["payment"]);
1010     if (!depositWidget || !paymentWidget)
1011         return;
1012 
1013     if (val.isNegative()) {
1014         paymentWidget->setValue(val.abs());
1015         depositWidget->setText(QString());
1016     } else {
1017         paymentWidget->setText(QString());
1018     }
1019     updateVAT();
1020 }
1021 
1022 void StdTransactionEditor::slotUpdateAmount(const QString& txt)
1023 {
1024     // qDebug("Update amount to %s", qPrintable(txt));
1025     MyMoneyMoney val(txt);
1026     updateAmount(val);
1027     updateVAT(true);
1028 }
1029 
1030 void StdTransactionEditor::updateAmount(const MyMoneyMoney& val)
1031 {
1032     // we don't do anything if we have multiple transactions selected
1033     if (isMultiSelection())
1034         return;
1035 
1036     Q_D(StdTransactionEditor);
1037     auto categoryLabel = dynamic_cast<QLabel*>(haveWidget("category-label"));
1038     if (categoryLabel) {
1039         if (auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(d->m_editWidgets["cashflow"])) {
1040             QSignalBlocker blockCashFlow(cashflow);
1041             if (val.isNegative()) {
1042                 cashflow->reverseDirection();
1043             }
1044             slotUpdateCashFlow(cashflow->direction());
1045             if (auto amountWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["amount"]))
1046                 amountWidget->setValue(val.abs());
1047         }
1048     }
1049 }
1050 
1051 void StdTransactionEditor::updateVAT(bool amountChanged)
1052 {
1053     Q_D(StdTransactionEditor);
1054     // make sure that we don't do this recursively
1055     if (d->m_inUpdateVat)
1056         return;
1057 
1058     // we don't do anything if we have multiple transactions selected
1059     if (isMultiSelection())
1060         return;
1061 
1062     // if auto vat assignment for this account is turned off
1063     // we don't care about taxes
1064     if (d->m_account.value("NoVat") == "Yes")
1065         return;
1066 
1067     // more splits than category and tax are not supported
1068     if (d->m_splits.count() > 2)
1069         return;
1070 
1071     // in order to do anything, we need an amount
1072     MyMoneyMoney amount, newAmount;
1073     bool amountOk;
1074     amount = amountFromWidget(&amountOk);
1075     if (!amountOk)
1076         return;
1077 
1078     // If the transaction has a tax and a category split, remove the tax split
1079     if (d->m_splits.count() == 2) {
1080         newAmount = removeVatSplit();
1081         if (d->m_splits.count() == 2) // not removed?
1082             return;
1083 
1084     } else if (auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"])) {
1085         // otherwise, we need a category
1086         if (category->selectedItem().isEmpty())
1087             return;
1088 
1089         // if no VAT account is associated with this category/account, then we bail out
1090         MyMoneyAccount cat = MyMoneyFile::instance()->account(category->selectedItem());
1091         if (cat.value("VatAccount").isEmpty())
1092             return;
1093 
1094         newAmount = amount;
1095     }
1096 
1097     // seems we have everything we need
1098     if (amountChanged)
1099         newAmount = amount;
1100 
1101     MyMoneyTransaction transaction;
1102     if (createTransaction(transaction, d->m_transaction, d->m_split)) {
1103         if (addVatSplit(transaction, newAmount)) {
1104             d->m_transaction = transaction;
1105             if (!d->m_transaction.splits().isEmpty())
1106                 d->m_split = d->m_transaction.splits().front();
1107 
1108             loadEditWidgets();
1109 
1110             // if we made this a split transaction, then move the
1111             // focus to the memo field
1112             if (qApp->focusWidget() == haveWidget("category")) {
1113                 QWidget* w = haveWidget("memo");
1114                 if (w)
1115                     w->setFocus();
1116             }
1117         }
1118     }
1119 }
1120 
1121 bool StdTransactionEditor::addVatSplit(MyMoneyTransaction& tr, const MyMoneyMoney& amount)
1122 {
1123     if (tr.splitCount() != 2)
1124         return false;
1125 
1126     Q_D(StdTransactionEditor);
1127     auto file = MyMoneyFile::instance();
1128     // extract the category split from the transaction
1129     MyMoneyAccount category = file->account(tr.splitByAccount(d->m_account.id(), false).accountId());
1130     return file->addVATSplit(tr, d->m_account, category, amount);
1131 }
1132 
1133 MyMoneyMoney StdTransactionEditor::removeVatSplit()
1134 {
1135     Q_D(StdTransactionEditor);
1136     // we only deal with splits that have three splits
1137     if (d->m_splits.count() != 2)
1138         return amountFromWidget();
1139 
1140     MyMoneySplit c; // category split
1141     MyMoneySplit t; // tax split
1142 
1143     auto netValue = false;
1144     foreach (const auto split, d->m_splits) {
1145         auto acc = MyMoneyFile::instance()->account(split.accountId());
1146         if (!acc.value("VatAccount").isEmpty()) {
1147             netValue = (acc.value("VatAmount").toLower() == "net");
1148             c = split;
1149         } else if (!acc.value("VatRate").isEmpty()) {
1150             t = split;
1151         }
1152     }
1153 
1154     // bail out if not all splits are setup
1155     if (c.id().isEmpty() || t.id().isEmpty())
1156         return amountFromWidget();
1157 
1158     MyMoneyMoney amount;
1159     // reduce the splits
1160     if (netValue) {
1161         amount = -c.shares();
1162     } else {
1163         amount = -(c.shares() + t.shares());
1164     }
1165 
1166     // remove tax split from the list, ...
1167     d->m_splits.clear();
1168     d->m_splits.append(c);
1169 
1170     // ... make sure that the widget is updated ...
1171     // block the signals to avoid popping up the split editor dialog
1172     // for nothing
1173     d->m_editWidgets["category"]->blockSignals(true);
1174     QString id;
1175     setupCategoryWidget(id);
1176     d->m_editWidgets["category"]->blockSignals(false);
1177 
1178     // ... and return the updated amount
1179     return amount;
1180 }
1181 
1182 bool StdTransactionEditor::isComplete(QString& reason) const
1183 {
1184     Q_D(const StdTransactionEditor);
1185 
1186     if (d->m_readOnly) {
1187         reason = i18n(
1188             "At least one split of the selected transaction references an account that has been closed. "
1189             "Editing the transactions is therefore prohibited.");
1190         return false;
1191     }
1192 
1193     reason.clear();
1194     QMap<QString, QWidget*>::const_iterator it_w;
1195 
1196     auto postDate = dynamic_cast<KMyMoneyDateInput*>(d->m_editWidgets["postdate"]);
1197     if (postDate) {
1198         QDate accountOpeningDate = d->m_account.openingDate();
1199         for (QList<MyMoneySplit>::const_iterator it_s = d->m_splits.constBegin(); it_s != d->m_splits.constEnd(); ++it_s) {
1200             const MyMoneyAccount& acc = MyMoneyFile::instance()->account((*it_s).accountId());
1201             // compute the newest opening date of all accounts involved in the transaction
1202             if (acc.openingDate() > accountOpeningDate)
1203                 accountOpeningDate = acc.openingDate();
1204         }
1205         // check the selected category in case m_splits hasn't been updated yet
1206         auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"]);
1207         if (category && !category->selectedItem().isEmpty()) {
1208             MyMoneyAccount cat = MyMoneyFile::instance()->account(category->selectedItem());
1209             if (cat.openingDate() > accountOpeningDate)
1210                 accountOpeningDate = cat.openingDate();
1211         }
1212 
1213         if (postDate->date().isValid() && (postDate->date() < accountOpeningDate)) {
1214             postDate->markAsBadDate(true, KMyMoneySettings::schemeColor(SchemeColor::Negative));
1215             reason = i18n("Cannot enter transaction with postdate prior to account's opening date.");
1216             postDate->setToolTip(reason);
1217             return false;
1218         }
1219         postDate->markAsBadDate();
1220         postDate->setToolTip(QString());
1221     }
1222 
1223     for (it_w = d->m_editWidgets.begin(); it_w != d->m_editWidgets.end(); ++it_w) {
1224         auto payee = dynamic_cast<KMyMoneyPayeeCombo*>(*it_w);
1225         auto tagContainer = dynamic_cast<KTagContainer*>(*it_w);
1226         auto category = dynamic_cast<KMyMoneyCategory*>(*it_w);
1227         auto amount = dynamic_cast<AmountEdit*>(*it_w);
1228         auto reconcile = dynamic_cast<KMyMoneyReconcileCombo*>(*it_w);
1229         auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(*it_w);
1230         auto memo = dynamic_cast<KTextEdit*>(*it_w);
1231 
1232         if (payee && !(payee->currentText().isEmpty()))
1233             break;
1234 
1235         if (category && !category->lineEdit()->text().isEmpty())
1236             break;
1237 
1238         if (amount && !(amount->value().isZero()))
1239             break;
1240 
1241         // the following widgets are only checked if we are editing multiple transactions
1242         if (isMultiSelection()) {
1243             if (auto tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(haveWidget("tabbar")))
1244                 tabbar->setEnabled(true);
1245 
1246             if (reconcile && reconcile->state() != eMyMoney::Split::State::Unknown)
1247                 break;
1248 
1249             if (cashflow && cashflow->direction() != eRegister::CashFlowDirection::Unknown)
1250                 break;
1251 
1252             if (postDate && postDate->date().isValid() && (postDate->date() >= d->m_account.openingDate()))
1253                 break;
1254 
1255             if (memo && d->m_memoChanged)
1256                 break;
1257 
1258             if (tagContainer && !(tagContainer->selectedTags().isEmpty()))  //  Tag is optional field
1259                 break;
1260         }
1261     }
1262     return it_w != d->m_editWidgets.end();
1263 }
1264 
1265 void StdTransactionEditor::slotCreateCategory(const QString& name, QString& id)
1266 {
1267     Q_D(StdTransactionEditor);
1268     MyMoneyAccount acc, parent;
1269     acc.setName(name);
1270 
1271     auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(haveWidget("cashflow"));
1272     if (cashflow) {
1273         // form based input
1274         if (cashflow->direction() == eRegister::CashFlowDirection::Deposit)
1275             parent = MyMoneyFile::instance()->income();
1276         else
1277             parent = MyMoneyFile::instance()->expense();
1278 
1279     } else if (haveWidget("deposit")) {
1280         // register based input
1281         if (auto deposit = dynamic_cast<AmountEdit*>(d->m_editWidgets["deposit"])) {
1282             if (deposit->value().isPositive())
1283                 parent = MyMoneyFile::instance()->income();
1284             else
1285                 parent = MyMoneyFile::instance()->expense();
1286         }
1287 
1288     } else
1289         parent = MyMoneyFile::instance()->expense();
1290 
1291     // TODO extract possible first part of a hierarchy and check if it is one
1292     // of our top categories. If so, remove it and select the parent
1293     // according to this information.
1294 
1295     slotNewCategory(acc, parent);
1296 
1297     // return id
1298     id = acc.id();
1299 }
1300 
1301 int StdTransactionEditor::slotEditSplits()
1302 {
1303     Q_D(StdTransactionEditor);
1304     int rc = QDialog::Rejected;
1305 
1306     if (!d->m_openEditSplits) {
1307         // only get in here in a single instance
1308         d->m_openEditSplits = true;
1309 
1310         // force focus change to update all data
1311         auto categoryWidget = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"]);
1312         QWidget* w = categoryWidget ? categoryWidget->splitButton() : nullptr;
1313         if (w)
1314             w->setFocus();
1315 
1316         auto amount = dynamic_cast<AmountEdit*>(haveWidget("amount"));
1317         auto deposit = dynamic_cast<AmountEdit*>(haveWidget("deposit"));
1318         auto payment = dynamic_cast<AmountEdit*>(haveWidget("payment"));
1319         KMyMoneyCashFlowCombo* cashflow = 0;
1320         eRegister::CashFlowDirection dir = eRegister::CashFlowDirection::Unknown;
1321         bool isValidAmount = false;
1322 
1323         if (amount) {
1324             isValidAmount = amount->text().length() != 0;
1325             if ((cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(haveWidget("cashflow"))))
1326                 dir = cashflow->direction();
1327 
1328         } else {
1329             if (deposit) {
1330                 if (deposit->text().length() != 0) {
1331                     isValidAmount = true;
1332                     dir = eRegister::CashFlowDirection::Deposit;
1333                 }
1334             }
1335             if (payment) {
1336                 if (payment->text().length() != 0) {
1337                     isValidAmount = true;
1338                     dir = eRegister::CashFlowDirection::Payment;
1339                 }
1340             }
1341             if (!deposit || !payment) {
1342                 qDebug("Internal error: deposit(%p) & payment(%p) widgets not found but required", deposit, payment);
1343                 return rc;
1344             }
1345         }
1346 
1347         if (dir == eRegister::CashFlowDirection::Unknown)
1348             dir = eRegister::CashFlowDirection::Payment;
1349 
1350         MyMoneyTransaction transaction;
1351         if (createTransaction(transaction, d->m_transaction, d->m_split)) {
1352             MyMoneyMoney value;
1353 
1354             QPointer<KSplitTransactionDlg> dlg =
1355                 new KSplitTransactionDlg(transaction,
1356                                          transaction.splits().isEmpty() ? MyMoneySplit() : transaction.splits().front(),
1357                                          d->m_account,
1358                                          isValidAmount,
1359                                          dir == eRegister::CashFlowDirection::Deposit,
1360                                          MyMoneyMoney(),
1361                                          d->m_priceInfo,
1362                                          d->m_regForm);
1363             connect(dlg.data(), &KSplitTransactionDlg::objectCreation, this, &StdTransactionEditor::objectCreation);
1364             connect(dlg.data(), &KSplitTransactionDlg::createCategory, this, &StdTransactionEditor::slotNewCategory);
1365 
1366             // propagate read-only mode
1367             dlg->setReadOnlyMode(d->m_readOnly);
1368 
1369             if ((rc = dlg->exec()) == QDialog::Accepted) {
1370                 d->m_transaction = dlg->transaction();
1371                 if (!d->m_transaction.splits().isEmpty()) {
1372                     d->m_split = d->m_transaction.splits().front();
1373                     // if we have only two splits left, we copy the memo
1374                     // of the second (data from the split editor) to the
1375                     // first (data used in the transaction editor)
1376                     if (d->m_transaction.splitCount() == 2) {
1377                         d->m_split.setMemo(d->m_transaction.splits().last().memo());
1378                         d->m_transaction.modifySplit(d->m_split);
1379                     }
1380                 }
1381                 loadEditWidgets();
1382             }
1383 
1384             delete dlg;
1385         }
1386 
1387         // focus jumps into the tag field
1388         if ((w = haveWidget("tag")) != 0) {
1389             w->setFocus();
1390         }
1391 
1392         d->m_openEditSplits = false;
1393     }
1394 
1395     return rc;
1396 }
1397 
1398 void StdTransactionEditor::checkPayeeInSplit(MyMoneySplit& s, const QString& payeeId)
1399 {
1400     if (s.accountId().isEmpty())
1401         return;
1402 
1403     auto acc = MyMoneyFile::instance()->account(s.accountId());
1404     if (acc.isIncomeExpense()) {
1405         s.setPayeeId(payeeId);
1406     } else {
1407         if (s.payeeId().isEmpty())
1408             s.setPayeeId(payeeId);
1409     }
1410 }
1411 
1412 MyMoneyMoney StdTransactionEditor::amountFromWidget(bool* update) const
1413 {
1414     Q_D(const StdTransactionEditor);
1415     bool updateValue = false;
1416     MyMoneyMoney value;
1417 
1418     auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(haveWidget("cashflow"));
1419     if (cashflow) {
1420         // form based input
1421         if (auto amount = dynamic_cast<AmountEdit*>(d->m_editWidgets["amount"])) {
1422             // if both fields do not contain changes -> no need to update
1423             if (cashflow->direction() != eRegister::CashFlowDirection::Unknown
1424                     && !amount->text().isEmpty())
1425                 updateValue = true;
1426             value = amount->value();
1427             if (cashflow->direction() == eRegister::CashFlowDirection::Payment)
1428                 value = -value;
1429         }
1430 
1431     } else if (haveWidget("deposit")) {
1432         // register based input
1433         auto deposit = dynamic_cast<AmountEdit*>(d->m_editWidgets["deposit"]);
1434         auto payment = dynamic_cast<AmountEdit*>(d->m_editWidgets["payment"]);
1435         if (deposit && payment) {
1436             // if both fields do not contain text -> no need to update
1437             if (!(deposit->text().isEmpty() && payment->text().isEmpty()))
1438                 updateValue = true;
1439 
1440             if (deposit->value().isPositive())
1441                 value = deposit->value();
1442             else
1443                 value = -(payment->value());
1444         }
1445     }
1446 
1447     if (update)
1448         *update = updateValue;
1449 
1450     // determine the max fraction for this account and
1451     // adjust the value accordingly
1452     return value.convert(d->m_account.fraction());
1453 }
1454 
1455 bool StdTransactionEditor::createTransaction(MyMoneyTransaction& t, const MyMoneyTransaction& torig, const MyMoneySplit& sorig, bool skipPriceDialog)
1456 {
1457     Q_D(StdTransactionEditor);
1458     // extract price info from original transaction
1459     d->m_priceInfo.clear();
1460     if (!torig.id().isEmpty()) {
1461         foreach (const auto split, torig.splits()) {
1462             if (split.id() != sorig.id()) {
1463                 MyMoneyAccount cat = MyMoneyFile::instance()->account(split.accountId());
1464                 if (cat.currencyId() != d->m_account.currencyId()) {
1465                     if (!split.shares().isZero() && !split.value().isZero()) {
1466                         d->m_priceInfo[cat.currencyId()] = (split.shares() / split.value()).reduce();
1467                     }
1468                 }
1469             }
1470         }
1471     }
1472 
1473     t = torig;
1474 
1475     t.removeSplits();
1476     t.setCommodity(d->m_account.currencyId());
1477 
1478     auto postDate = dynamic_cast<KMyMoneyDateInput*>(d->m_editWidgets["postdate"]);
1479     if (postDate && postDate->date().isValid()) {
1480         t.setPostDate(postDate->date());
1481     }
1482 
1483     // we start with the previous values, make sure we can add them later on
1484     MyMoneySplit s0 = sorig;
1485     s0.clearId();
1486 
1487     // make sure we reference this account here
1488     s0.setAccountId(d->m_account.id());
1489 
1490     // memo and number field are special: if we have multiple transactions selected
1491     // and the edit field is empty, we treat it as "not modified".
1492     // FIXME a better approach would be to have a 'dirty' flag with the widgets
1493     //       which identifies if the originally loaded value has been modified
1494     //       by the user
1495     auto memo = dynamic_cast<KTextEdit*>(d->m_editWidgets["memo"]);
1496     if (memo) {
1497         if (!isMultiSelection() || d->m_memoChanged)
1498             s0.setMemo(memo->toPlainText());
1499     }
1500 
1501     if (auto number = dynamic_cast<KMyMoneyLineEdit*>(haveWidget("number"))) {
1502         if (!isMultiSelection() || !number->text().isEmpty())
1503             s0.setNumber(number->text());
1504     }
1505 
1506     auto payee = dynamic_cast<KMyMoneyPayeeCombo*>(d->m_editWidgets["payee"]);
1507     QString payeeId;
1508     if (payee && (!isMultiSelection() || !payee->currentText().isEmpty())) {
1509         payeeId = payee->selectedItem();
1510         s0.setPayeeId(payeeId);
1511     }
1512 
1513     //KMyMoneyTagCombo* tag = dynamic_cast<KMyMoneyTagCombo*>(m_editWidgets["tag"]);
1514     auto tag = dynamic_cast<KTagContainer*>(d->m_editWidgets["tag"]);
1515     if (tag && (!isMultiSelection() || !tag->selectedTags().isEmpty())) {
1516         s0.setTagIdList(tag->selectedTags());
1517     }
1518 
1519     bool updateValue;
1520     MyMoneyMoney value = amountFromWidget(&updateValue);
1521 
1522     if (updateValue) {
1523         // for this account, the shares and value is the same
1524         s0.setValue(value);
1525         s0.setShares(value);
1526     } else {
1527         value = s0.value();
1528     }
1529 
1530     // if we mark the split reconciled here, we'll use today's date if no reconciliation date is given
1531     auto status = dynamic_cast<KMyMoneyReconcileCombo*>(d->m_editWidgets["status"]);
1532     if (status && status->state() != eMyMoney::Split::State::Unknown)
1533         s0.setReconcileFlag(status->state());
1534 
1535     if (s0.reconcileFlag() == eMyMoney::Split::State::Reconciled && !s0.reconcileDate().isValid())
1536         s0.setReconcileDate(QDate::currentDate());
1537 
1538     checkPayeeInSplit(s0, payeeId);
1539 
1540     // add the split to the transaction
1541     t.addSplit(s0);
1542 
1543     // if we have no other split we create it
1544     // if we have none or only one other split, we reconstruct it here
1545     // if we have more than one other split, we take them as they are
1546     // make sure to perform all those changes on a local copy
1547     QList<MyMoneySplit> splits = d->m_splits;
1548 
1549     MyMoneySplit s1;
1550     if (splits.isEmpty()) {
1551         s1.setMemo(s0.memo());
1552         splits.append(s1);
1553 
1554         // make sure we will fill the value and share fields later on
1555         updateValue = true;
1556     }
1557 
1558     // FIXME in multiSelection we currently only support transactions with one
1559     // or two splits. So we check the original transaction and extract the other
1560     // split or create it
1561     if (isMultiSelection()) {
1562         if (torig.splitCount() == 2) {
1563             foreach (const auto split, torig.splits()) {
1564                 if (split.id() == sorig.id())
1565                     continue;
1566                 s1 = split;
1567                 s1.clearId();
1568                 break;
1569             }
1570         }
1571     } else {
1572         if (splits.count() == 1) {
1573             s1 = splits[0];
1574             s1.clearId();
1575         }
1576     }
1577 
1578     if (isMultiSelection() || splits.count() == 1) {
1579         auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"]);
1580         if (category && (!isMultiSelection() || !category->currentText().isEmpty())) {
1581             s1.setAccountId(category->selectedItem());
1582         }
1583 
1584         // if the first split has a memo but the second split is empty,
1585         // we just copy the memo text over
1586         if (memo) {
1587             if (!isMultiSelection() || !memo->toPlainText().isEmpty()) {
1588                 // if the memo is filled, we check if the
1589                 // account referenced by s1 is a regular account or a category.
1590                 // in case of a regular account, we just leave the memo as is
1591                 // in case of a category we simply copy the new value over the old.
1592                 // in case we don't even have an account id, we just skip because
1593                 // the split will be removed later on anyway.
1594                 if (!s1.memo().isEmpty() && s1.memo() != s0.memo()) {
1595                     if (!s1.accountId().isEmpty()) {
1596                         auto acc = MyMoneyFile::instance()->account(s1.accountId());
1597                         if (acc.isIncomeExpense())
1598                             s1.setMemo(s0.memo());
1599                         else if (KMessageBox::questionYesNo(d->m_regForm,
1600                                                             i18n("Do you want to replace memo<p><i>%1</i></p>with memo<p><i>%2</i></p>in the other split?", s1.memo(), s0.memo()), i18n("Copy memo"),
1601                                                             KStandardGuiItem::yes(), KStandardGuiItem::no(),
1602                                                             QStringLiteral("CopyMemoOver")) == KMessageBox::Yes)
1603                             s1.setMemo(s0.memo());
1604                     }
1605                 } else {
1606                     s1.setMemo(s0.memo());
1607                 }
1608             }
1609         }
1610 
1611         if (updateValue && !s1.accountId().isEmpty()) {
1612             s1.setValue(-value);
1613             MyMoneyMoney shares;
1614             if (!skipPriceDialog) {
1615                 if (!KCurrencyCalculator::setupSplitPrice(shares, t, s1, d->m_priceInfo, d->m_regForm))
1616                     return false;
1617             } else {
1618                 MyMoneyAccount cat = MyMoneyFile::instance()->account(s1.accountId());
1619                 if (d->m_priceInfo.find(cat.currencyId()) != d->m_priceInfo.end()) {
1620                     shares = (s1.value() * d->m_priceInfo[cat.currencyId()]).reduce().convert(cat.fraction());
1621                 } else
1622                     shares = s1.value();
1623             }
1624             s1.setShares(shares);
1625         }
1626 
1627         checkPayeeInSplit(s1, payeeId);
1628 
1629         if (!s1.accountId().isEmpty())
1630             t.addSplit(s1);
1631 
1632         // check if we need to add/update a VAT assignment
1633         MyMoneyFile::instance()->updateVAT(t);
1634 
1635     } else {
1636         foreach (const auto split, splits) {
1637             s1 = split;
1638             s1.clearId();
1639             checkPayeeInSplit(s1, payeeId);
1640             t.addSplit(s1);
1641         }
1642     }
1643     return true;
1644 }
1645 
1646 void StdTransactionEditor::setupFinalWidgets()
1647 {
1648     addFinalWidget(haveWidget("deposit"));
1649     addFinalWidget(haveWidget("payment"));
1650     addFinalWidget(haveWidget("amount"));
1651     addFinalWidget(haveWidget("status"));
1652 }
1653 
1654 void StdTransactionEditor::slotUpdateAccount(const QString& id)
1655 {
1656     Q_D(StdTransactionEditor);
1657     TransactionEditor::slotUpdateAccount(id);
1658     auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"]);
1659     if (category && category->splitButton()) {
1660         category->splitButton()->setDisabled(id.isEmpty());
1661     }
1662 }