File indexing completed on 2024-05-19 16:14:52

0001 /*
0002     SPDX-FileCopyrightText: 2007-2019 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 "investtransactioneditor.h"
0008 #include "transactioneditor_p.h"
0009 
0010 #include <typeinfo>
0011 
0012 // ----------------------------------------------------------------------------
0013 // QT Includes
0014 
0015 #include <QLabel>
0016 #include <QList>
0017 #include <QPushButton>
0018 
0019 // ----------------------------------------------------------------------------
0020 // KDE Includes
0021 
0022 #include <KTextEdit>
0023 #include <KLocalizedString>
0024 
0025 // ----------------------------------------------------------------------------
0026 // Project Includes
0027 
0028 #include "kmymoneyreconcilecombo.h"
0029 #include "kmymoneyactivitycombo.h"
0030 #include "kmymoneytagcombo.h"
0031 #include "ktagcontainer.h"
0032 #include "investtransaction.h"
0033 #include "selectedtransactions.h"
0034 #include "transactioneditorcontainer.h"
0035 #include "kmymoneycategory.h"
0036 #include "kmymoneydateinput.h"
0037 #include "amountedit.h"
0038 #include "kmymoneyaccountselector.h"
0039 #include "kmymoneymvccombo.h"
0040 #include "mymoneyfile.h"
0041 #include "mymoneyexception.h"
0042 #include "mymoneysecurity.h"
0043 #include "mymoneyprice.h"
0044 #include "ksplittransactiondlg.h"
0045 #include "kcurrencycalculator.h"
0046 #include "kmymoneysettings.h"
0047 #include "investactivities.h"
0048 #include "kmymoneycompletion.h"
0049 #include "dialogenums.h"
0050 
0051 using namespace eMyMoney;
0052 using namespace KMyMoneyRegister;
0053 using namespace KMyMoneyTransactionForm;
0054 using namespace Invest;
0055 
0056 class InvestTransactionEditorPrivate : public TransactionEditorPrivate
0057 {
0058     Q_DISABLE_COPY(InvestTransactionEditorPrivate)
0059     Q_DECLARE_PUBLIC(InvestTransactionEditor)
0060     friend class Invest::Activity;
0061 
0062 public:
0063     explicit  InvestTransactionEditorPrivate(InvestTransactionEditor* qq) :
0064         TransactionEditorPrivate(qq),
0065         m_activity(nullptr),
0066         m_phonyAccount(MyMoneyAccount("Phony-ID", MyMoneyAccount())),
0067         m_transactionType(eMyMoney::Split::InvestmentTransactionType::BuyShares)
0068     {
0069     }
0070 
0071     ~InvestTransactionEditorPrivate()
0072     {
0073         delete m_activity;
0074     }
0075 
0076     void showCategory(const QString& name, bool visible = true)
0077     {
0078         Q_Q(InvestTransactionEditor);
0079         if (auto cat = dynamic_cast<KMyMoneyCategory*>(q->haveWidget(name))) {
0080             if (Q_LIKELY(cat->splitButton())) {
0081                 cat->parentWidget()->setVisible(visible);  // show or hide the enclosing QFrame;
0082             } else {
0083                 cat->setVisible(visible);  // show or hide the enclosing QFrame;
0084             }
0085         }
0086     }
0087 
0088     void activityFactory(eMyMoney::Split::InvestmentTransactionType type)
0089     {
0090         Q_Q(InvestTransactionEditor);
0091         if (!m_activity || type != m_activity->type()) {
0092             delete m_activity;
0093             switch (type) {
0094             default:
0095             case eMyMoney::Split::InvestmentTransactionType::BuyShares:
0096                 m_activity = new Buy(q);
0097                 break;
0098             case eMyMoney::Split::InvestmentTransactionType::SellShares:
0099                 m_activity = new Sell(q);
0100                 break;
0101             case eMyMoney::Split::InvestmentTransactionType::Dividend:
0102             case eMyMoney::Split::InvestmentTransactionType::Yield:
0103                 m_activity = new Div(q);
0104                 break;
0105             case eMyMoney::Split::InvestmentTransactionType::ReinvestDividend:
0106                 m_activity = new Reinvest(q);
0107                 break;
0108             case eMyMoney::Split::InvestmentTransactionType::AddShares:
0109                 m_activity = new Add(q);
0110                 break;
0111             case eMyMoney::Split::InvestmentTransactionType::RemoveShares:
0112                 m_activity = new Remove(q);
0113                 break;
0114             case eMyMoney::Split::InvestmentTransactionType::SplitShares:
0115                 m_activity = new Invest::Split(q);
0116                 break;
0117             case eMyMoney::Split::InvestmentTransactionType::InterestIncome:
0118                 m_activity = new IntInc(q);
0119                 break;
0120             }
0121         }
0122     }
0123 
0124     MyMoneyMoney subtotal(const QList<MyMoneySplit>& splits) const
0125     {
0126         MyMoneyMoney sum;
0127 
0128         foreach (const auto split, splits)
0129             sum += split.value();
0130 
0131         return sum;
0132     }
0133 
0134     /**
0135      * This method creates a transaction to be used for the split fee/interest editor.
0136      * It has a reference to a phony account and the splits contained in @a splits .
0137      */
0138     bool createPseudoTransaction(MyMoneyTransaction& t, const QList<MyMoneySplit>& splits)
0139     {
0140         t.removeSplits();
0141 
0142         MyMoneySplit split;
0143         split.setAccountId(m_phonyAccount.id());
0144         split.setValue(-subtotal(splits));
0145         split.setShares(split.value());
0146         t.addSplit(split);
0147         m_phonySplit = split;
0148 
0149         foreach (const auto it_s, splits) {
0150             split = it_s;
0151             split.clearId();
0152             t.addSplit(split);
0153         }
0154         return true;
0155     }
0156 
0157     /**
0158      * Convenience method used by slotEditInterestSplits() and slotEditFeeSplits().
0159      *
0160      * @param categoryWidgetName name of the category widget
0161      * @param amountWidgetName name of the amount widget
0162      * @param splits the splits that make up the transaction to be edited
0163      * @param isIncome @c false for fees, @c true for interest
0164      * @param slotEditSplits name of the slot to be connected to the focusIn signal of the
0165      *                       category widget named @p categoryWidgetName in case of multiple splits
0166      *                       in @p splits .
0167      */
0168 
0169     int editSplits(const QString& categoryWidgetName,
0170                    const QString& amountWidgetName,
0171                    QList<MyMoneySplit>& splits,
0172                    bool isIncome,
0173                    const char* slotEditSplits)
0174     {
0175         Q_Q(InvestTransactionEditor);
0176         int rc = QDialog::Rejected;
0177 
0178         if (!m_openEditSplits) {
0179             // only get in here in a single instance
0180             m_openEditSplits = true;
0181 
0182             // force focus change to update all data
0183             auto category = dynamic_cast<KMyMoneyCategory*>(m_editWidgets[categoryWidgetName]);
0184             if (!category)
0185                 return rc;
0186             QWidget* w = category->splitButton();
0187             if (w)
0188                 w->setFocus();
0189 
0190             auto amount = dynamic_cast<AmountEdit*>(q->haveWidget(amountWidgetName));
0191             if (!amount)
0192                 return rc;
0193 
0194             MyMoneyTransaction transaction;
0195             transaction.setCommodity(m_currency.id());
0196             if (splits.count() == 0 && !category->selectedItem().isEmpty()) {
0197                 MyMoneySplit s;
0198                 s.setAccountId(category->selectedItem());
0199                 s.setShares(amount->value());
0200                 s.setValue(s.shares());
0201                 splits << s;
0202             }
0203             // use the transactions commodity as the currency indicator for the splits
0204             // this is used to allow some useful setting for the fractions in the amount fields
0205             try {
0206                 m_phonyAccount.setCurrencyId(m_transaction.commodity());
0207                 m_phonyAccount.fraction(MyMoneyFile::instance()->security(m_transaction.commodity()));
0208             } catch (const MyMoneyException &) {
0209                 qDebug("Unable to setup precision");
0210             }
0211 
0212             if (createPseudoTransaction(transaction, splits)) {
0213                 MyMoneyMoney value;
0214 
0215                 QPointer<KSplitTransactionDlg> dlg = new KSplitTransactionDlg(transaction,
0216                         m_phonySplit,
0217                         m_phonyAccount,
0218                         false,
0219                         isIncome,
0220                         MyMoneyMoney(),
0221                         m_priceInfo,
0222                         m_regForm);
0223                 // q->connect(dlg, SIGNAL(newCategory(MyMoneyAccount&)), q, SIGNAL(newCategory(MyMoneyAccount&)));
0224 
0225                 // propagate read-only mode
0226                 dlg->setReadOnlyMode(m_readOnly);
0227 
0228                 if ((rc = dlg->exec()) == QDialog::Accepted) {
0229                     transaction = dlg->transaction();
0230                     // collect splits out of the transaction
0231                     splits.clear();
0232                     MyMoneyMoney fees;
0233                     foreach (const auto split, transaction.splits()) {
0234                         if (split.accountId() == m_phonyAccount.id())
0235                             continue;
0236                         splits << split;
0237                         fees += split.shares();
0238                     }
0239                     if (isIncome)
0240                         fees = -fees;
0241 
0242                     QString categoryId;
0243                     q->setupCategoryWidget(category, splits, categoryId, slotEditSplits);
0244                     amount->setValue(fees);
0245                     q->slotUpdateTotalAmount();
0246                 }
0247 
0248                 delete dlg;
0249             }
0250 
0251             // focus jumps into the memo field
0252             if ((w = q->haveWidget("memo")) != nullptr) {
0253                 w->setFocus();
0254             }
0255 
0256             m_openEditSplits = false;
0257         }
0258         return rc;
0259     }
0260 
0261     void updatePriceMode(const MyMoneySplit& split = MyMoneySplit())
0262     {
0263         Q_Q(InvestTransactionEditor);
0264         if (auto label = dynamic_cast<QLabel*>(q->haveWidget("price-label"))) {
0265             auto sharesEdit = dynamic_cast<AmountEdit*>(q->haveWidget("shares"));
0266             auto priceEdit = dynamic_cast<AmountEdit*>(q->haveWidget("price"));
0267 
0268             if (!sharesEdit || !priceEdit)
0269                 return;
0270 
0271             MyMoneyMoney price;
0272             if (!split.id().isEmpty())
0273                 price = split.price().reduce();
0274             else
0275                 price = priceEdit->value().abs();
0276 
0277             if (q->priceMode() == eDialogs::PriceMode::PricePerTransaction) {
0278                 priceEdit->setPrecision(m_currency.pricePrecision());
0279                 label->setText(i18n("Transaction amount"));
0280                 if (!sharesEdit->value().isZero())
0281                     priceEdit->setValue(sharesEdit->value().abs() * price);
0282 
0283             } else if (q->priceMode() == eDialogs::PriceMode::PricePerShare) {
0284                 priceEdit->setPrecision(m_security.pricePrecision());
0285                 label->setText(i18n("Price/Share"));
0286                 priceEdit->setValue(price);
0287             } else
0288                 priceEdit->setValue(price);
0289         }
0290     }
0291 
0292     Activity*                        m_activity;
0293     MyMoneyAccount                   m_phonyAccount;
0294     MyMoneySplit                     m_phonySplit;
0295     MyMoneySplit                     m_assetAccountSplit;
0296     QList<MyMoneySplit>              m_interestSplits;
0297     QList<MyMoneySplit>              m_feeSplits;
0298     MyMoneySecurity                  m_security;
0299     MyMoneySecurity                  m_currency;
0300     eMyMoney::Split::InvestmentTransactionType m_transactionType;
0301 };
0302 
0303 
0304 InvestTransactionEditor::InvestTransactionEditor() :
0305     TransactionEditor(*new InvestTransactionEditorPrivate(this))
0306 {
0307     Q_D(InvestTransactionEditor);
0308     d->m_transactionType = eMyMoney::Split::InvestmentTransactionType::UnknownTransactionType;
0309 }
0310 
0311 InvestTransactionEditor::~InvestTransactionEditor()
0312 {
0313 }
0314 
0315 InvestTransactionEditor::InvestTransactionEditor(TransactionEditorContainer* regForm,
0316         KMyMoneyRegister::InvestTransaction* item,
0317         const KMyMoneyRegister::SelectedTransactions& list,
0318         const QDate& lastPostDate) :
0319     TransactionEditor(*new InvestTransactionEditorPrivate(this),
0320                       regForm,
0321                       item,
0322                       list,
0323                       lastPostDate)
0324 {
0325     Q_D(InvestTransactionEditor);
0326     // after the geometries of the container are updated hide the widgets which are not needed by the current activity
0327     connect(d->m_regForm, &TransactionEditorContainer::geometriesUpdated, this, &InvestTransactionEditor::slotTransactionContainerGeometriesUpdated);
0328 
0329     // dissect the transaction into its type, splits, currency, security etc.
0330     KMyMoneyUtils::dissectTransaction(d->m_transaction, d->m_split,
0331                                       d->m_assetAccountSplit,
0332                                       d->m_feeSplits,
0333                                       d->m_interestSplits,
0334                                       d->m_security,
0335                                       d->m_currency,
0336                                       d->m_transactionType);
0337 
0338     // determine initial activity object
0339     d->activityFactory(d->m_transactionType);
0340 }
0341 
0342 void InvestTransactionEditor::createEditWidgets()
0343 {
0344     Q_D(InvestTransactionEditor);
0345     auto activity = new KMyMoneyActivityCombo();
0346     activity->setObjectName("activity");
0347     d->m_editWidgets["activity"] = activity;
0348     connect(activity, &KMyMoneyActivityCombo::activitySelected, this, &InvestTransactionEditor::slotUpdateActivity);
0349     connect(activity, &KMyMoneyActivityCombo::activitySelected, this, &InvestTransactionEditor::slotUpdateButtonState);
0350 
0351     auto postDate = d->m_editWidgets["postdate"] = new KMyMoneyDateInput;
0352     connect(postDate, SIGNAL(dateChanged(QDate)), this, SLOT(slotUpdateButtonState()));
0353 
0354     auto security = new KMyMoneySecurity;
0355     security->setObjectName("security");
0356     security->setPlaceholderText(i18n("Security"));
0357     d->m_editWidgets["security"] = security;
0358     connect(security, &KMyMoneyCombo::itemSelected, this, &InvestTransactionEditor::slotUpdateSecurity);
0359     connect(security, &QComboBox::editTextChanged, this, &InvestTransactionEditor::slotUpdateButtonState);
0360     connect(security, &KMyMoneyCombo::createItem, this, &InvestTransactionEditor::slotCreateSecurity);
0361     connect(security, &KMyMoneyCombo::objectCreation, this, &InvestTransactionEditor::objectCreation);
0362 
0363     auto asset = new KMyMoneyCategory(false, nullptr);
0364     asset->setObjectName("asset-account");
0365     asset->setPlaceholderText(i18n("Asset account"));
0366     d->m_editWidgets["asset-account"] = asset;
0367     connect(asset, &QComboBox::editTextChanged, this, &InvestTransactionEditor::slotUpdateButtonState);
0368     connect(asset, &KMyMoneyCombo::objectCreation, this, &InvestTransactionEditor::objectCreation);
0369 
0370     auto fees = new KMyMoneyCategory(true, nullptr);
0371     fees->setObjectName("fee-account");
0372     fees->setPlaceholderText(i18n("Fees"));
0373     d->m_editWidgets["fee-account"] = fees;
0374     connect(fees, &KMyMoneyCombo::itemSelected, this, &InvestTransactionEditor::slotUpdateFeeCategory);
0375     connect(fees, &QComboBox::editTextChanged, this, &InvestTransactionEditor::slotUpdateButtonState);
0376     connect(fees, &KMyMoneyCombo::createItem, this, &InvestTransactionEditor::slotCreateFeeCategory);
0377     connect(fees, &KMyMoneyCombo::objectCreation, this, &InvestTransactionEditor::objectCreation);
0378     connect(fees->splitButton(), &QAbstractButton::clicked, this, &InvestTransactionEditor::slotEditFeeSplits);
0379 
0380     auto interest = new KMyMoneyCategory(true, nullptr);
0381     interest->setPlaceholderText(i18n("Interest"));
0382     interest->setObjectName("interest-account");
0383     d->m_editWidgets["interest-account"] = interest;
0384     connect(interest, &KMyMoneyCombo::itemSelected, this, &InvestTransactionEditor::slotUpdateInterestCategory);
0385     connect(interest, &QComboBox::editTextChanged, this, &InvestTransactionEditor::slotUpdateButtonState);
0386     connect(interest, &KMyMoneyCombo::createItem, this, &InvestTransactionEditor::slotCreateInterestCategory);
0387     connect(interest, &KMyMoneyCombo::objectCreation, this, &InvestTransactionEditor::objectCreation);
0388     connect(interest->splitButton(), &QAbstractButton::clicked, this, &InvestTransactionEditor::slotEditInterestSplits);
0389 
0390     auto tag = new KTagContainer;
0391     tag->tagCombo()->setPlaceholderText(i18n("Tag"));
0392     tag->tagCombo()->setObjectName(QLatin1String("tag"));
0393     d->m_editWidgets["tag"] = tag;
0394     connect(tag->tagCombo(), &QComboBox::editTextChanged, this, &InvestTransactionEditor::slotUpdateButtonState);
0395     connect(tag->tagCombo(), &KMyMoneyMVCCombo::createItem, this, &InvestTransactionEditor::slotNewTag);
0396     connect(tag->tagCombo(), &KMyMoneyMVCCombo::objectCreation, this, &InvestTransactionEditor::objectCreation);
0397 
0398     auto memo = new KTextEdit;
0399     memo->setObjectName("memo");
0400     memo->setTabChangesFocus(true);
0401     d->m_editWidgets["memo"] = memo;
0402     connect(memo, &QTextEdit::textChanged, this, &InvestTransactionEditor::slotUpdateInvestMemoState);
0403     connect(memo, &QTextEdit::textChanged, this, &InvestTransactionEditor::slotUpdateButtonState);
0404 
0405     d->m_activity->memoText().clear();
0406     d->m_activity->memoChanged() = false;
0407 
0408     AmountEdit* value = new AmountEdit;
0409     value->setObjectName("shares");
0410     value->setPlaceholderText(i18n("Shares"));
0411     value->setCalculatorButtonVisible(true);
0412     d->m_editWidgets["shares"] = value;
0413     connect(value, &AmountEdit::textChanged, this, &InvestTransactionEditor::slotUpdateButtonState);
0414     connect(value, &AmountEdit::valueChanged, this, &InvestTransactionEditor::slotUpdateTotalAmount);
0415 
0416     value = new AmountEdit;
0417     value->setObjectName("price");
0418     value->setPlaceholderText(i18n("Price"));
0419     value->setCalculatorButtonVisible(true);
0420     d->m_editWidgets["price"] = value;
0421     connect(value, &AmountEdit::textChanged, this, &InvestTransactionEditor::slotUpdateButtonState);
0422     connect(value, &AmountEdit::valueChanged, this, &InvestTransactionEditor::slotUpdateTotalAmount);
0423 
0424     value = new AmountEdit;
0425     value->setObjectName("fee-amount");
0426     // TODO once we have the selected transactions as array of Transaction
0427     // we can allow multiple splits for fee and interest
0428     value->setCalculatorButtonVisible(true);
0429     d->m_editWidgets["fee-amount"] = value;
0430     connect(value, &AmountEdit::textChanged, this, &InvestTransactionEditor::slotUpdateButtonState);
0431     connect(value, &AmountEdit::valueChanged, this, &InvestTransactionEditor::slotUpdateTotalAmount);
0432 
0433     value = new AmountEdit;
0434     value->setObjectName("interest-amount");
0435     // TODO once we have the selected transactions as array of Transaction
0436     // we can allow multiple splits for fee and interest
0437     value->setCalculatorButtonVisible(true);
0438     d->m_editWidgets["interest-amount"] = value;
0439     connect(value, &AmountEdit::textChanged, this, &InvestTransactionEditor::slotUpdateButtonState);
0440     connect(value, &AmountEdit::valueChanged, this, &InvestTransactionEditor::slotUpdateTotalAmount);
0441 
0442     auto reconcile = new KMyMoneyReconcileCombo;
0443     reconcile->setObjectName("reconcile");
0444     d->m_editWidgets["status"] = reconcile;
0445     connect(reconcile, &KMyMoneyMVCCombo::itemSelected, this, &InvestTransactionEditor::slotUpdateButtonState);
0446 
0447     KMyMoneyRegister::QWidgetContainer::iterator it_w;
0448     for (it_w = d->m_editWidgets.begin(); it_w != d->m_editWidgets.end(); ++it_w) {
0449         (*it_w)->installEventFilter(this);
0450     }
0451 
0452     QLabel* label;
0453 
0454     d->m_editWidgets["activity-label"] = label = new QLabel(i18n("Activity"));
0455     label->setAlignment(Qt::AlignVCenter);
0456 
0457     d->m_editWidgets["postdate-label"] = label = new QLabel(i18n("Date"));
0458     label->setAlignment(Qt::AlignVCenter);
0459 
0460     d->m_editWidgets["security-label"] = label = new QLabel(i18n("Security"));
0461     label->setAlignment(Qt::AlignVCenter);
0462 
0463     d->m_editWidgets["shares-label"] = label = new QLabel(i18n("Shares"));
0464     label->setAlignment(Qt::AlignVCenter);
0465 
0466     d->m_editWidgets["asset-label"] = label = new QLabel(i18n("Account"));
0467     label->setAlignment(Qt::AlignVCenter);
0468 
0469     d->m_editWidgets["price-label"] = label = new QLabel(i18n("Price/share"));
0470     label->setAlignment(Qt::AlignVCenter);
0471 
0472     d->m_editWidgets["fee-label"] = label = new QLabel(i18n("Fees"));
0473     label->setAlignment(Qt::AlignVCenter);
0474 
0475     d->m_editWidgets["fee-amount-label"] = label = new QLabel(QString());
0476     label->setAlignment(Qt::AlignVCenter);
0477 
0478     d->m_editWidgets["interest-label"] = label = new QLabel(i18n("Interest"));
0479     label->setAlignment(Qt::AlignVCenter);
0480 
0481     d->m_editWidgets["interest-amount-label"] = label = new QLabel(i18n("Interest"));
0482     label->setAlignment(Qt::AlignVCenter);
0483 
0484     d->m_editWidgets["memo-label"] = label = new QLabel(i18n("Memo"));
0485     label->setAlignment(Qt::AlignVCenter);
0486 
0487     d->m_editWidgets["total"] = label = new QLabel(QString());
0488     label->setAlignment(Qt::AlignVCenter | Qt::AlignRight);
0489 
0490     d->m_editWidgets["total-label"] = label = new QLabel(i18nc("Total value", "Total"));
0491     label->setAlignment(Qt::AlignVCenter);
0492 
0493     d->m_editWidgets["status-label"] = label = new QLabel(i18n("Status"));
0494     label->setAlignment(Qt::AlignVCenter);
0495 
0496     // if we don't have more than 1 selected transaction, we don't need
0497     // the "don't change" item in some of the combo widgets
0498     if (d->m_transactions.count() < 2) {
0499         reconcile->removeDontCare();
0500     }
0501 }
0502 
0503 int InvestTransactionEditor::slotEditFeeSplits()
0504 {
0505     Q_D(InvestTransactionEditor);
0506     return d->editSplits("fee-account", "fee-amount", d->m_feeSplits, false, SLOT(slotEditFeeSplits()));
0507 }
0508 
0509 int InvestTransactionEditor::slotEditInterestSplits()
0510 {
0511     Q_D(InvestTransactionEditor);
0512     return d->editSplits("interest-account", "interest-amount", d->m_interestSplits, true, SLOT(slotEditInterestSplits()));
0513 }
0514 
0515 void InvestTransactionEditor::slotCreateSecurity(const QString& name, QString& id)
0516 {
0517     Q_D(InvestTransactionEditor);
0518     MyMoneyAccount acc;
0519     QRegExp exp("([^:]+)");
0520     if (exp.indexIn(name) != -1) {
0521         acc.setName(exp.cap(1));
0522 
0523         slotNewInvestment(acc, d->m_account);
0524 
0525         // return id
0526         id = acc.id();
0527 
0528         if (!id.isEmpty()) {
0529             slotUpdateSecurity(id);
0530             slotReloadEditWidgets();
0531         }
0532     }
0533 }
0534 
0535 void InvestTransactionEditor::slotCreateFeeCategory(const QString& name, QString& id)
0536 {
0537     MyMoneyAccount acc;
0538     acc.setName(name);
0539 
0540     slotNewCategory(acc, MyMoneyFile::instance()->expense());
0541 
0542     // return id
0543     id = acc.id();
0544 }
0545 
0546 void InvestTransactionEditor::slotUpdateFeeCategory(const QString& id)
0547 {
0548     haveWidget("fee-amount")->setDisabled(id.isEmpty());
0549 }
0550 
0551 void InvestTransactionEditor::slotUpdateInterestCategory(const QString& id)
0552 {
0553     haveWidget("interest-amount")->setDisabled(id.isEmpty());
0554 }
0555 
0556 void InvestTransactionEditor::slotCreateInterestCategory(const QString& name, QString& id)
0557 {
0558     MyMoneyAccount acc;
0559     acc.setName(name);
0560 
0561     slotNewCategory(acc, MyMoneyFile::instance()->income());
0562 
0563     id = acc.id();
0564 }
0565 
0566 void InvestTransactionEditor::slotReloadEditWidgets()
0567 {
0568     Q_D(InvestTransactionEditor);
0569     auto interest = dynamic_cast<KMyMoneyCategory*>(haveWidget("interest-account"));
0570     auto fees = dynamic_cast<KMyMoneyCategory*>(haveWidget("fee-account"));
0571     auto security = dynamic_cast<KMyMoneySecurity*>(haveWidget("security"));
0572 
0573     if (!interest || !fees || !security)
0574         return;
0575 
0576     AccountSet aSet;
0577     QString id;
0578 
0579     // interest-account
0580     aSet.clear();
0581     aSet.addAccountGroup(Account::Type::Income);
0582     aSet.load(interest->selector());
0583     setupCategoryWidget(interest, d->m_interestSplits, id, SLOT(slotEditInterestSplits()));
0584 
0585     // fee-account
0586     aSet.clear();
0587     aSet.addAccountGroup(Account::Type::Expense);
0588     aSet.load(fees->selector());
0589     setupCategoryWidget(fees, d->m_feeSplits, id, SLOT(slotEditFeeSplits()));
0590 
0591     // security
0592     aSet.clear();
0593     aSet.load(security->selector(), i18n("Security"), d->m_account.accountList(), true);
0594 }
0595 
0596 void InvestTransactionEditor::loadEditWidgets(eWidgets::eRegister::Action)
0597 {
0598     loadEditWidgets();
0599 }
0600 
0601 void InvestTransactionEditor::loadEditWidgets()
0602 {
0603     Q_D(InvestTransactionEditor);
0604     QString id;
0605 
0606     auto postDate = dynamic_cast<KMyMoneyDateInput*>(haveWidget("postdate"));
0607     auto reconcile = dynamic_cast<KMyMoneyReconcileCombo*>(haveWidget("status"));
0608     auto security = dynamic_cast<KMyMoneySecurity*>(haveWidget("security"));
0609     auto activity = dynamic_cast<KMyMoneyActivityCombo*>(haveWidget("activity"));
0610     auto asset = dynamic_cast<KMyMoneyCategory*>(haveWidget("asset-account"));
0611     auto memo = dynamic_cast<KTextEdit*>(d->m_editWidgets["memo"]);
0612     AmountEdit* value;
0613     auto interest = dynamic_cast<KMyMoneyCategory*>(haveWidget("interest-account"));
0614     auto fees = dynamic_cast<KMyMoneyCategory*>(haveWidget("fee-account"));
0615 
0616     if (!postDate || !reconcile || !security || !activity ||
0617             !asset || !memo || !interest || !fees)
0618         return;
0619 
0620     // check if the current transaction has a reference to an equity account
0621     auto haveEquityAccount = false;
0622     foreach (const auto split, d->m_transaction.splits()) {
0623         auto acc = MyMoneyFile::instance()->account(split.accountId());
0624         if (acc.accountType() == Account::Type::Equity) {
0625             haveEquityAccount = true;
0626             break;
0627         }
0628     }
0629 
0630     // asset-account
0631     AccountSet aSet;
0632     aSet.clear();
0633     aSet.addAccountType(Account::Type::Checkings);
0634     aSet.addAccountType(Account::Type::Savings);
0635     aSet.addAccountType(Account::Type::Cash);
0636     aSet.addAccountType(Account::Type::Asset);
0637     aSet.addAccountType(Account::Type::Currency);
0638     aSet.addAccountType(Account::Type::CreditCard);
0639     if (KMyMoneySettings::expertMode() || haveEquityAccount)
0640         aSet.addAccountGroup(Account::Type::Equity);
0641     aSet.load(asset->selector());
0642 
0643     // security
0644     security->setSuppressObjectCreation(false);    // allow object creation on the fly
0645     aSet.clear();
0646     aSet.load(security->selector(), i18n("Security"), d->m_account.accountList(), true);
0647 
0648     // memo
0649     memo->setText(d->m_split.memo());
0650     d->m_activity->memoText() = d->m_split.memo();
0651     d->m_activity->memoChanged() = false;
0652 
0653     if (!isMultiSelection()) {
0654         // date
0655         if (d->m_transaction.postDate().isValid())
0656             postDate->setDate(d->m_transaction.postDate());
0657         else if (d->m_lastPostDate.isValid())
0658             postDate->setDate(d->m_lastPostDate);
0659         else
0660             postDate->setDate(QDate::currentDate());
0661 
0662         // security (but only if it's not the investment account)
0663         if (d->m_split.accountId() != d->m_account.id()) {
0664             security->completion()->setSelected(d->m_split.accountId());
0665             security->slotItemSelected(d->m_split.accountId());
0666         }
0667 
0668         // activity
0669         activity->setActivity(d->m_activity->type());
0670         slotUpdateActivity(activity->activity());
0671 
0672         asset->completion()->setSelected(d->m_assetAccountSplit.accountId());
0673         asset->slotItemSelected(d->m_assetAccountSplit.accountId());
0674 
0675         // interest-account
0676         aSet.clear();
0677         aSet.addAccountGroup(Account::Type::Income);
0678         aSet.load(interest->selector());
0679         setupCategoryWidget(interest, d->m_interestSplits, id, SLOT(slotEditInterestSplits()));
0680 
0681         // fee-account
0682         aSet.clear();
0683         aSet.addAccountGroup(Account::Type::Expense);
0684         aSet.load(fees->selector());
0685         setupCategoryWidget(fees, d->m_feeSplits, id, SLOT(slotEditFeeSplits()));
0686 
0687         // shares
0688         // don't set the value if the number of shares is zero so that
0689         // we can see the hint
0690         value = dynamic_cast<AmountEdit*>(haveWidget("shares"));
0691         if (!value)
0692             return;
0693         if (typeid(*(d->m_activity)) != typeid(Invest::Split(this)))
0694             value->setPrecision(MyMoneyMoney::denomToPrec(d->m_security.smallestAccountFraction()));
0695         else
0696             value->setPrecision(-1);
0697 
0698         if (!d->m_split.shares().isZero())
0699             value->setValue(d->m_split.shares().abs());
0700 
0701         // price
0702         d->updatePriceMode(d->m_split);
0703 
0704         // fee amount
0705         value = dynamic_cast<AmountEdit*>(haveWidget("fee-amount"));
0706         if (!value)
0707             return;
0708         value->setPrecision(MyMoneyMoney::denomToPrec(d->m_currency.smallestAccountFraction()));
0709         value->setValue(d->subtotal(d->m_feeSplits));
0710 
0711         // interest amount
0712         value = dynamic_cast<AmountEdit*>(haveWidget("interest-amount"));
0713         if (!value)
0714             return;
0715         value->setPrecision(MyMoneyMoney::denomToPrec(d->m_currency.smallestAccountFraction()));
0716         value->setValue(-d->subtotal(d->m_interestSplits));
0717 
0718         // total
0719         slotUpdateTotalAmount();
0720 
0721         // status
0722         if (d->m_split.reconcileFlag() == eMyMoney::Split::State::Unknown)
0723             d->m_split.setReconcileFlag(eMyMoney::Split::State::NotReconciled);
0724         reconcile->setState(d->m_split.reconcileFlag());
0725 
0726     } else {
0727         postDate->loadDate(QDate());
0728         reconcile->setState(eMyMoney::Split::State::Unknown);
0729 
0730         // We don't allow to change the activity
0731         activity->setActivity(d->m_activity->type());
0732         slotUpdateActivity(activity->activity());
0733         activity->setDisabled(true);
0734 
0735         // scan the list of selected transactions and check that they have
0736         // the same activity.
0737         const QString& action = d->m_item->split().action();
0738         bool isNegative = d->m_item->split().shares().isNegative();
0739         bool allSameActivity = true;
0740         for (auto it_t = d->m_transactions.begin(); allSameActivity && (it_t != d->m_transactions.end()); ++it_t) {
0741             allSameActivity = (action == (*it_t).split().action() && (*it_t).split().shares().isNegative() == isNegative);
0742         }
0743 
0744         QStringList fields;
0745         fields << "shares" << "price" << "fee-amount" << "interest-amount";
0746         for (auto it_f = fields.constBegin(); it_f != fields.constEnd(); ++it_f) {
0747             value = dynamic_cast<AmountEdit*>(haveWidget((*it_f)));
0748             if (!value)
0749                 return;
0750             value->setText("");
0751             value->setAllowEmpty();
0752         }
0753 
0754         // if we have transactions with different activities, disable some more widgets
0755         if (!allSameActivity) {
0756             fields << "asset-account" << "fee-account" << "interest-account";
0757             for (auto it_f = fields.constBegin(); it_f != fields.constEnd(); ++it_f) {
0758                 haveWidget(*it_f)->setDisabled(true);
0759             }
0760         }
0761     }
0762 }
0763 
0764 QWidget* InvestTransactionEditor::firstWidget() const
0765 {
0766     return nullptr; // let the creator use the first widget in the tab order
0767 }
0768 
0769 bool InvestTransactionEditor::isComplete(QString& reason) const
0770 {
0771     Q_D(const InvestTransactionEditor);
0772     reason.clear();
0773 
0774     if (d->m_readOnly) {
0775         reason = i18n(
0776             "At least one split of the selected transaction references an account that has been closed. "
0777             "Editing the transactions is therefore prohibited.");
0778         return false;
0779     }
0780 
0781     auto postDate = dynamic_cast<KMyMoneyDateInput*>(d->m_editWidgets["postdate"]);
0782     if (postDate) {
0783         QDate accountOpeningDate = d->m_account.openingDate();
0784         auto asset = dynamic_cast<KMyMoneyCategory*>(haveWidget("asset-account"));
0785         if (asset && asset->isVisible()) {
0786             if (!isMultiSelection() || !asset->currentText().isEmpty()) {
0787                 const auto assetId = asset->selectedItem();
0788                 if (!assetId.isEmpty()) {
0789                     try {
0790                         const auto acc = MyMoneyFile::instance()->account(assetId);
0791                         if (acc.openingDate() > accountOpeningDate)
0792                             accountOpeningDate = acc.openingDate();
0793                     } catch(MyMoneyException& e) {
0794                         qDebug() << "opening date check failed on account" << assetId << e.what();
0795                     }
0796                 }
0797             }
0798         }
0799 
0800         if (postDate->date().isValid() && (postDate->date() < accountOpeningDate)) {
0801             postDate->markAsBadDate(true, KMyMoneySettings::schemeColor(SchemeColor::Negative));
0802             reason = i18n("Cannot enter transaction with postdate prior to account's opening date.");
0803             postDate->setToolTip(reason);
0804             return false;
0805         }
0806         postDate->markAsBadDate();
0807         postDate->setToolTip(QString());
0808     }
0809 
0810     return d->m_activity->isComplete(reason);
0811 }
0812 
0813 void InvestTransactionEditor::slotUpdateSecurity(const QString& stockId)
0814 {
0815     Q_D(InvestTransactionEditor);
0816     auto file = MyMoneyFile::instance();
0817     MyMoneyAccount stock = file->account(stockId);
0818     d->m_security = file->security(stock.currencyId());
0819     d->m_currency = file->security(d->m_security.tradingCurrency());
0820     bool currencyKnown = !d->m_currency.id().isEmpty();
0821     if (!currencyKnown) {
0822         d->m_currency.setTradingSymbol("???");
0823     } else {
0824         auto sharesWidget = dynamic_cast<AmountEdit*>(haveWidget("shares"));
0825         if (sharesWidget) {
0826             if (typeid(*(d->m_activity)) != typeid(Invest::Split(this)))
0827                 sharesWidget->setPrecision(MyMoneyMoney::denomToPrec(d->m_security.smallestAccountFraction()));
0828             else
0829                 sharesWidget->setPrecision(-1);
0830         }
0831     }
0832 
0833     d->updatePriceMode();
0834 
0835     d->m_activity->preloadAssetAccount();
0836 
0837     haveWidget("shares")->setEnabled(currencyKnown);
0838     haveWidget("price")->setEnabled(currencyKnown);
0839     haveWidget("fee-amount")->setEnabled(currencyKnown);
0840     haveWidget("interest-amount")->setEnabled(currencyKnown);
0841 
0842     slotUpdateTotalAmount();
0843     slotUpdateButtonState();
0844     resizeForm();
0845 }
0846 
0847 bool InvestTransactionEditor::fixTransactionCommodity(const MyMoneyAccount& /* account */)
0848 {
0849     return true;
0850 }
0851 
0852 
0853 MyMoneyMoney InvestTransactionEditor::totalAmount() const
0854 {
0855     MyMoneyMoney amount;
0856 
0857     auto activityCombo = dynamic_cast<KMyMoneyActivityCombo*>(haveWidget("activity"));
0858     auto sharesEdit = dynamic_cast<AmountEdit*>(haveWidget("shares"));
0859     auto priceEdit = dynamic_cast<AmountEdit*>(haveWidget("price"));
0860     auto feesEdit = dynamic_cast<AmountEdit*>(haveWidget("fee-amount"));
0861     auto interestEdit = dynamic_cast<AmountEdit*>(haveWidget("interest-amount"));
0862 
0863     if (!activityCombo || !sharesEdit || !priceEdit ||
0864             !feesEdit || !interestEdit)
0865         return amount;
0866 
0867     if (priceMode() == eDialogs::PriceMode::PricePerTransaction)
0868         amount = priceEdit->value().abs();
0869     else
0870         amount = sharesEdit->value().abs() * priceEdit->value().abs();
0871 
0872     if (feesEdit->isVisible()) {
0873         MyMoneyMoney fee = feesEdit->value();
0874         MyMoneyMoney factor(-1, 1);
0875         switch (activityCombo->activity()) {
0876         case eMyMoney::Split::InvestmentTransactionType::BuyShares:
0877         case eMyMoney::Split::InvestmentTransactionType::ReinvestDividend:
0878             factor = MyMoneyMoney::ONE;
0879             break;
0880         default:
0881             break;
0882         }
0883         amount += (fee * factor);
0884     }
0885 
0886     if (interestEdit->isVisible()) {
0887         MyMoneyMoney interest = interestEdit->value();
0888         MyMoneyMoney factor(1, 1);
0889         switch (activityCombo->activity()) {
0890         case eMyMoney::Split::InvestmentTransactionType::BuyShares:
0891             factor = MyMoneyMoney::MINUS_ONE;
0892             break;
0893         default:
0894             break;
0895         }
0896         amount += (interest * factor);
0897     }
0898     return amount;
0899 }
0900 
0901 void InvestTransactionEditor::slotUpdateTotalAmount()
0902 {
0903     Q_D(InvestTransactionEditor);
0904     auto total = dynamic_cast<QLabel*>(haveWidget("total"));
0905 
0906     if (total && total->isVisible()) {
0907         total->setText(totalAmount().convert(d->m_currency.smallestAccountFraction(), d->m_security.roundingMethod())
0908                        .formatMoney(d->m_currency.tradingSymbol(), MyMoneyMoney::denomToPrec(d->m_currency.smallestAccountFraction())));
0909     }
0910 }
0911 
0912 void InvestTransactionEditor::slotTransactionContainerGeometriesUpdated()
0913 {
0914     Q_D(InvestTransactionEditor);
0915     // when the geometries of the transaction container are updated some edit widgets that were
0916     // previously hidden are being shown (see QAbstractItemView::updateEditorGeometries) so we
0917     // need to update the activity with the current activity in order to show only the widgets
0918     // which are needed by the current activity
0919     if (d->m_editWidgets.isEmpty())
0920         return;
0921     slotUpdateActivity(d->m_activity->type());
0922 }
0923 
0924 void InvestTransactionEditor::slotUpdateActivity(eMyMoney::Split::InvestmentTransactionType activity)
0925 {
0926     Q_D(InvestTransactionEditor);
0927     // create new activity object if required
0928     d->activityFactory(activity);
0929 
0930     // hide all dynamic widgets
0931     d->showCategory("interest-account", false);
0932     d->showCategory("fee-account", false);
0933 
0934     QStringList dynwidgets= {
0935         "total-label",
0936         "asset-label",
0937         "fee-label",
0938         "fee-amount-label",
0939         "interest-label",
0940         "interest-amount-label",
0941         "price-label",
0942         "shares-label",
0943     };
0944 
0945     // hiding labels works by clearing them. hide() does not do the job
0946     // as the underlying text in the QTable object will shine through
0947     QStringList::const_iterator it_s;
0948     for (it_s = dynwidgets.constBegin(); it_s != dynwidgets.constEnd(); ++it_s) {
0949         QLabel* w = dynamic_cast<QLabel*>(haveWidget(*it_s));
0950         if (w)
0951             w->setText(QStringLiteral(" "));
0952     }
0953 
0954     // real widgets can be hidden
0955     dynwidgets.clear();
0956     dynwidgets = QStringList{"asset-account", "interest-amount", "fee-amount", "shares", "price", "total"};
0957 
0958     for (it_s = dynwidgets.constBegin(); it_s != dynwidgets.constEnd(); ++it_s) {
0959         QWidget* w = haveWidget(*it_s);
0960         if (w)
0961             w->hide();
0962     }
0963     d->m_activity->showWidgets();
0964     d->m_activity->preloadAssetAccount();
0965 }
0966 
0967 eDialogs::PriceMode InvestTransactionEditor::priceMode() const
0968 {
0969     Q_D(const InvestTransactionEditor);
0970     eDialogs::PriceMode mode = static_cast<eDialogs::PriceMode>(eDialogs::PriceMode::Price);
0971     auto sec = dynamic_cast<KMyMoneySecurity*>(d->m_editWidgets["security"]);
0972 
0973     QString accId;
0974     if (sec && !sec->currentText().isEmpty()) {
0975         accId = sec->selectedItem();
0976         if (accId.isEmpty())
0977             accId = d->m_account.id();
0978     }
0979     while (!accId.isEmpty() && mode == eDialogs::PriceMode::Price) {
0980         auto acc = MyMoneyFile::instance()->account(accId);
0981         if (acc.value("priceMode").isEmpty())
0982             accId = acc.parentAccountId();
0983         else
0984             mode = static_cast<eDialogs::PriceMode>(acc.value("priceMode").toInt());
0985     }
0986 
0987     // if mode is still <Price> then use that
0988     if (mode == eDialogs::PriceMode::Price)
0989         mode = eDialogs::PriceMode::PricePerShare;
0990     return mode;
0991 }
0992 
0993 MyMoneySecurity InvestTransactionEditor::security() const
0994 {
0995     Q_D(const InvestTransactionEditor);
0996     return d->m_security;
0997 }
0998 
0999 QList<MyMoneySplit> InvestTransactionEditor::feeSplits() const
1000 {
1001     Q_D(const InvestTransactionEditor);
1002     return d->m_feeSplits;
1003 }
1004 
1005 QList<MyMoneySplit> InvestTransactionEditor::interestSplits() const
1006 {
1007     Q_D(const InvestTransactionEditor);
1008     return d->m_interestSplits;
1009 }
1010 
1011 bool InvestTransactionEditor::setupPrice(const MyMoneyTransaction& t, MyMoneySplit& split)
1012 {
1013     Q_D(InvestTransactionEditor);
1014     auto file = MyMoneyFile::instance();
1015     auto acc = file->account(split.accountId());
1016     MyMoneySecurity toCurrency(file->security(acc.currencyId()));
1017     int fract = acc.fraction();
1018 
1019     if (acc.currencyId() != t.commodity()) {
1020         if (acc.currencyId().isEmpty())
1021             acc.setCurrencyId(t.commodity());
1022 
1023         QMap<QString, MyMoneyMoney>::Iterator it_p;
1024         QString key = t.commodity() + '-' + acc.currencyId();
1025         it_p = d->m_priceInfo.find(key);
1026 
1027         // if it's not found, then collect it from the user first
1028         MyMoneyMoney price;
1029         if (it_p == d->m_priceInfo.end()) {
1030             MyMoneySecurity fromCurrency = file->security(t.commodity());
1031             MyMoneyMoney fromValue, toValue;
1032 
1033             fromValue = split.value();
1034             const MyMoneyPrice &priceInfo = MyMoneyFile::instance()->price(fromCurrency.id(), toCurrency.id(), t.postDate());
1035             toValue = split.value() * priceInfo.rate(toCurrency.id());
1036 
1037             QPointer<KCurrencyCalculator> calc =
1038                 new KCurrencyCalculator(fromCurrency,
1039                                         toCurrency,
1040                                         fromValue,
1041                                         toValue,
1042                                         t.postDate(),
1043                                         10000000000,
1044                                         d->m_regForm);
1045 
1046             if (calc->exec() == QDialog::Rejected) {
1047                 delete calc;
1048                 return false;
1049             }
1050             price = calc->price();
1051             delete calc;
1052             d->m_priceInfo[key] = price;
1053         } else {
1054             price = (*it_p);
1055         }
1056 
1057         // update shares if the transaction commodity is the currency
1058         // of the current selected account
1059         split.setShares((split.value() * price).convert(fract));
1060     } else {
1061         split.setShares(split.value());
1062     }
1063 
1064     return true;
1065 }
1066 
1067 bool InvestTransactionEditor::createTransaction(MyMoneyTransaction& t, const MyMoneyTransaction& torig, const MyMoneySplit& sorig, bool /* skipPriceDialog */)
1068 {
1069     Q_D(InvestTransactionEditor);
1070     auto file = MyMoneyFile::instance();
1071     // we start with the previous values, make sure we can add them later on
1072     t = torig;
1073     MyMoneySplit s0 = sorig;
1074     s0.clearId();
1075 
1076     auto sec = dynamic_cast<KMyMoneySecurity*>(d->m_editWidgets["security"]);
1077     if (sec && (!isMultiSelection() || !sec->currentText().isEmpty())) {
1078         QString securityId = sec->selectedItem();
1079         if (!securityId.isEmpty()) {
1080             s0.setAccountId(securityId);
1081             MyMoneyAccount stockAccount = file->account(securityId);
1082             QString currencyId = stockAccount.currencyId();
1083             MyMoneySecurity security = file->security(currencyId);
1084 
1085             t.setCommodity(security.tradingCurrency());
1086         } else {
1087             s0.setAccountId(d->m_account.id());
1088             t.setCommodity(d->m_account.currencyId());
1089         }
1090     }
1091 
1092     // extract price info from original transaction
1093     d->m_priceInfo.clear();
1094     if (!torig.id().isEmpty()) {
1095         foreach (const auto split, torig.splits()) {
1096             if (split.id() != sorig.id()) {
1097                 auto cat = file->account(split.accountId());
1098                 if (cat.currencyId() != t.commodity()) {
1099                     if (cat.currencyId().isEmpty())
1100                         cat.setCurrencyId(d->m_account.currencyId());
1101                     if (!split.shares().isZero() && !split.value().isZero()) {
1102                         d->m_priceInfo[cat.currencyId()] = (split.shares() / split.value()).reduce();
1103                     }
1104                 }
1105             }
1106         }
1107     }
1108 
1109     t.removeSplits();
1110 
1111     auto postDate = dynamic_cast<KMyMoneyDateInput*>(d->m_editWidgets["postdate"]);
1112     if (postDate && postDate->date().isValid()) {
1113         t.setPostDate(postDate->date());
1114     }
1115 
1116     // memo and number field are special: if we have multiple transactions selected
1117     // and the edit field is empty, we treat it as "not modified".
1118     // FIXME a better approach would be to have a 'dirty' flag with the widgets
1119     //       which identifies if the originally loaded value has been modified
1120     //       by the user
1121     auto memo = dynamic_cast<KTextEdit*>(d->m_editWidgets["memo"]);
1122     if (memo) {
1123         if (!isMultiSelection() || d->m_activity->memoChanged())
1124             s0.setMemo(memo->toPlainText());
1125     }
1126 
1127     MyMoneySplit assetAccountSplit;
1128     QList<MyMoneySplit> feeSplits;
1129     QList<MyMoneySplit> interestSplits;
1130     MyMoneySecurity security;
1131     MyMoneySecurity transactionCurrency = file->security(t.commodity());
1132     eMyMoney::Split::InvestmentTransactionType transactionType;
1133 
1134     // extract the splits from the original transaction, but only
1135     // if there is one because otherwise the currency is overridden
1136     if (torig.splitCount() != 0) {
1137         KMyMoneyUtils::dissectTransaction(torig, sorig,
1138                                           assetAccountSplit,
1139                                           feeSplits,
1140                                           interestSplits,
1141                                           security,
1142                                           transactionCurrency,
1143                                           transactionType);
1144     }
1145     // check if the trading currency is the same if the security has changed
1146     // in case it differs, check that we have a price (request from user)
1147     // and convert all splits
1148     // TODO
1149 
1150     // do the conversions here
1151     // TODO
1152 
1153     // keep the current activity object and create a new one
1154     // that can be destroyed later on
1155     auto activity = d->m_activity;
1156     d->m_activity = nullptr;      // make sure we create a new one
1157     d->activityFactory(activity->type());
1158 
1159     // if the activity is not set in the combo widget, we keep
1160     // the one which is used in the original transaction
1161     auto activityCombo = dynamic_cast<KMyMoneyActivityCombo*>(haveWidget("activity"));
1162     if (activityCombo && activityCombo->activity() == eMyMoney::Split::InvestmentTransactionType::UnknownTransactionType) {
1163         d->activityFactory(transactionType);
1164     }
1165 
1166     // if we mark the split reconciled here, we'll use today's date if no reconciliation date is given
1167     auto status = dynamic_cast<KMyMoneyReconcileCombo*>(d->m_editWidgets["status"]);
1168     if (status && status->state() != eMyMoney::Split::State::Unknown)
1169         s0.setReconcileFlag(status->state());
1170 
1171     if (s0.reconcileFlag() == eMyMoney::Split::State::Reconciled && !s0.reconcileDate().isValid())
1172         s0.setReconcileDate(QDate::currentDate());
1173 
1174     // call the creation logic for the current selected activity
1175     bool rc = d->m_activity->createTransaction(t, s0, assetAccountSplit, feeSplits, d->m_feeSplits, interestSplits, d->m_interestSplits, security, transactionCurrency);
1176 
1177     // now switch back to the original activity
1178     delete d->m_activity;
1179     d->m_activity = activity;
1180 
1181     // add the splits to the transaction
1182     if (rc) {
1183         if (security.name().isEmpty())                                              // new transaction has no security filled...
1184             security = file->security(file->account(s0.accountId()).currencyId());    // ...so fetch it from s0 split
1185 
1186         QList<MyMoneySplit> resultSplits;  // concatenates splits for easy processing
1187 
1188         if (!assetAccountSplit.accountId().isEmpty())
1189             resultSplits.append(assetAccountSplit);
1190 
1191         if (!feeSplits.isEmpty())
1192             resultSplits.append(feeSplits);
1193 
1194         if (!interestSplits.isEmpty())
1195             resultSplits.append(interestSplits);
1196 
1197         AlkValue::RoundingMethod roundingMethod = AlkValue::RoundRound;
1198         if (security.roundingMethod() != AlkValue::RoundNever)
1199             roundingMethod = security.roundingMethod();
1200 
1201         int transactionCurrencyFraction = transactionCurrency.smallestAccountFraction();
1202         int securityFraction = security.smallestAccountFraction();
1203 
1204         // assuming that all non-stock splits are monetary
1205         for (auto& split : resultSplits) {
1206             const auto acc = file->account(split.accountId());
1207             const auto currency = file->security(acc.currencyId());
1208             const auto splitCurrencyFraction = currency.smallestAccountFraction();
1209 
1210             split.clearId();
1211             split.setShares(MyMoneyMoney(split.shares().convertDenominator(splitCurrencyFraction, currency.roundingMethod())));
1212             split.setValue(MyMoneyMoney(split.value().convertDenominator(transactionCurrencyFraction, transactionCurrency.roundingMethod())));
1213             t.addSplit(split);
1214         }
1215 
1216         // Don't do any rounding on a split factor
1217         if (d->m_activity->type() != eMyMoney::Split::InvestmentTransactionType::SplitShares) {
1218             s0.setShares(MyMoneyMoney(s0.shares().convertDenominator(securityFraction, roundingMethod))); // only shares variable from stock split isn't evaluated in currency
1219             s0.setValue(MyMoneyMoney(s0.value().convertDenominator(transactionCurrencyFraction, transactionCurrency.roundingMethod())));
1220         }
1221         t.addSplit(s0);
1222     }
1223 
1224     return rc;
1225 }
1226 
1227 void InvestTransactionEditor::setupFinalWidgets()
1228 {
1229     addFinalWidget(haveWidget("memo"));
1230 }
1231 
1232 void InvestTransactionEditor::slotUpdateInvestMemoState()
1233 {
1234     Q_D(InvestTransactionEditor);
1235     auto memo = dynamic_cast<KTextEdit*>(d->m_editWidgets["memo"]);
1236     if (memo) {
1237         d->m_activity->memoChanged() = (memo->toPlainText() != d->m_activity->memoText());
1238     }
1239 }