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 }