File indexing completed on 2024-05-12 16:42:01
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 "investactivities.h" 0008 0009 // ---------------------------------------------------------------------------- 0010 // QT Includes 0011 0012 #include <QLabel> 0013 #include <QList> 0014 0015 // ---------------------------------------------------------------------------- 0016 // KDE Includes 0017 0018 #include <KLocalizedString> 0019 0020 // ---------------------------------------------------------------------------- 0021 // Project Includes 0022 0023 #include "investtransactioneditor.h" 0024 #include "mymoneymoney.h" 0025 #include "kmymoneycategory.h" 0026 #include "amountedit.h" 0027 #include "kmymoneyaccountselector.h" 0028 #include "kmymoneycompletion.h" 0029 #include <kmymoneysettings.h> 0030 #include "mymoneyfile.h" 0031 #include "mymoneysplit.h" 0032 #include "mymoneyaccount.h" 0033 #include "mymoneysecurity.h" 0034 #include "dialogenums.h" 0035 #include "mymoneyenums.h" 0036 0037 using namespace Invest; 0038 using namespace KMyMoneyRegister; 0039 0040 class Invest::ActivityPrivate 0041 { 0042 Q_DISABLE_COPY(ActivityPrivate) 0043 0044 public: 0045 ActivityPrivate() : 0046 m_parent(nullptr), 0047 m_memoChanged(false) 0048 { 0049 } 0050 0051 InvestTransactionEditor *m_parent; 0052 QMap<QString, MyMoneyMoney> m_priceInfo; 0053 bool m_memoChanged; 0054 QString m_memoText; 0055 }; 0056 0057 0058 Activity::Activity(InvestTransactionEditor* editor) : 0059 d_ptr(new ActivityPrivate) 0060 { 0061 Q_D(Activity); 0062 d->m_memoChanged = false; 0063 d->m_parent = editor; 0064 } 0065 0066 Activity::~Activity() 0067 { 0068 Q_D(Activity); 0069 delete d; 0070 } 0071 0072 bool& Activity::memoChanged() 0073 { 0074 Q_D(Activity); 0075 return d->m_memoChanged; 0076 } 0077 0078 QString& Activity::memoText() 0079 { 0080 Q_D(Activity); 0081 return d->m_memoText; 0082 } 0083 0084 bool Activity::isComplete(QString& reason) const 0085 { 0086 Q_D(const Activity); 0087 Q_UNUSED(reason) 0088 0089 auto rc = false; 0090 auto security = dynamic_cast<KMyMoneySecurity*>(haveWidget("security")); 0091 if (security && !security->currentText().isEmpty()) { 0092 rc = (security->selector()->contains(security->currentText()) || (isMultiSelection() && d->m_memoChanged)); 0093 } 0094 return rc; 0095 } 0096 0097 QWidget* Activity::haveWidget(const QString& name) const 0098 { 0099 Q_D(const Activity); 0100 return d->m_parent->haveWidget(name); 0101 } 0102 0103 bool Activity::haveAssetAccount() const 0104 { 0105 auto rc = true; 0106 auto cat = dynamic_cast<KMyMoneyCategory*>(haveWidget("asset-account")); 0107 if (!cat) 0108 return false; 0109 0110 if (!isMultiSelection()) 0111 rc = !cat->currentText().isEmpty(); 0112 0113 if (rc && !cat->currentText().isEmpty()) 0114 rc = cat->selector()->contains(cat->currentText()); 0115 0116 return rc; 0117 } 0118 0119 bool Activity::haveCategoryAndAmount(const QString& category, const QString& amount, bool optional) const 0120 { 0121 Q_D(const Activity); 0122 auto cat = dynamic_cast<KMyMoneyCategory*>(haveWidget(category)); 0123 0124 auto rc = true; 0125 0126 if (cat && !cat->currentText().isEmpty()) { 0127 rc = cat->selector()->contains(cat->currentText()) || cat->isSplitTransaction(); 0128 if (rc && !amount.isEmpty() && !isMultiSelection()) { 0129 if (cat->isSplitTransaction()) { 0130 QList<MyMoneySplit>::const_iterator split; 0131 QList<MyMoneySplit>::const_iterator splitEnd; 0132 0133 if (category == "fee-account") { 0134 split = d->m_parent->feeSplits().cbegin(); 0135 splitEnd = d->m_parent->feeSplits().cend(); 0136 } else if (category == "interest-account") { 0137 split = d->m_parent->interestSplits().cbegin(); 0138 splitEnd = d->m_parent->interestSplits().cend(); 0139 } 0140 0141 for (; split != splitEnd; ++split) { 0142 if ((*split).value().isZero()) 0143 rc = false; 0144 } 0145 } else { 0146 if (auto valueWidget = dynamic_cast<AmountEdit*>(haveWidget(amount))) 0147 rc = !valueWidget->value().isZero(); 0148 } 0149 } 0150 } else if (!isMultiSelection() && !optional) { 0151 rc = false; 0152 } 0153 return rc; 0154 } 0155 0156 bool Activity::haveFees(bool optional) const 0157 { 0158 return haveCategoryAndAmount("fee-account", "fee-amount", optional); 0159 } 0160 0161 bool Activity::haveInterest(bool optional) const 0162 { 0163 return haveCategoryAndAmount("interest-account", "interest-amount", optional); 0164 } 0165 0166 bool Activity::haveShares() const 0167 { 0168 if (auto amount = dynamic_cast<AmountEdit*>(haveWidget("shares"))) { 0169 if (isMultiSelection() && amount->value().isZero()) 0170 return true; 0171 0172 return !amount->value().isZero(); 0173 } 0174 return false; 0175 } 0176 0177 bool Activity::havePrice() const 0178 { 0179 if (auto amount = dynamic_cast<AmountEdit*>(haveWidget("price"))) { 0180 if (isMultiSelection() && amount->value().isZero()) 0181 return true; 0182 0183 return !amount->value().isZero(); 0184 } 0185 return false; 0186 } 0187 0188 bool Activity::isMultiSelection() const 0189 { 0190 Q_D(const Activity); 0191 return d->m_parent->isMultiSelection(); 0192 } 0193 0194 bool Activity::createCategorySplits(const MyMoneyTransaction& t, KMyMoneyCategory* cat, AmountEdit* amount, MyMoneyMoney factor, QList<MyMoneySplit>&splits, const QList<MyMoneySplit>& osplits) const 0195 { 0196 Q_D(const Activity); 0197 auto rc = true; 0198 if (!isMultiSelection() || !cat->currentText().isEmpty()) { 0199 if (!cat->isSplitTransaction()) { 0200 splits.clear(); 0201 MyMoneySplit s1; 0202 // base the resulting split on the original split 0203 // that was provided which avoids loosing data 0204 // stored in the split 0205 if (!osplits.isEmpty()) { 0206 s1 = osplits.first(); 0207 s1.clearId(); 0208 } 0209 QString categoryId; 0210 categoryId = cat->selectedItem(); 0211 if (!categoryId.isEmpty()) { 0212 s1.setAccountId(categoryId); 0213 s1.setValue(amount->value() * factor); 0214 if (!s1.value().isZero()) { 0215 rc = d->m_parent->setupPrice(t, s1); 0216 } 0217 splits.append(s1); 0218 } 0219 } else { 0220 splits = osplits; 0221 } 0222 } 0223 return rc; 0224 } 0225 0226 void Activity::createAssetAccountSplit(MyMoneySplit& split, const MyMoneySplit& stockSplit) const 0227 { 0228 auto cat = dynamic_cast<KMyMoneyCategory*>(haveWidget("asset-account")); 0229 if (cat && (!isMultiSelection() || !cat->currentText().isEmpty())) { 0230 auto categoryId = cat->selectedItem(); 0231 split.setAccountId(categoryId); 0232 } 0233 split.setMemo(stockSplit.memo()); 0234 } 0235 0236 MyMoneyMoney Activity::sumSplits(const MyMoneySplit& s0, const QList<MyMoneySplit>& feeSplits, const QList<MyMoneySplit>& interestSplits) const 0237 { 0238 auto total = s0.value(); 0239 foreach (const auto feeSplit, feeSplits) 0240 total += feeSplit.value(); 0241 0242 foreach (const auto interestSplit, interestSplits) 0243 total += interestSplit.value(); 0244 0245 return total; 0246 } 0247 0248 void Activity::setLabelText(const QString& idx, const QString& txt) const 0249 { 0250 auto w = dynamic_cast<QLabel*>(haveWidget(idx)); 0251 if (w) { 0252 w->setText(txt); 0253 } else { 0254 if (KMyMoneySettings::transactionForm()) { 0255 // labels are only used in the transaction form 0256 qDebug("Unknown QLabel named '%s'", qPrintable(idx)); 0257 } 0258 } 0259 } 0260 0261 void Activity::preloadAssetAccount() 0262 { 0263 Q_D(Activity); 0264 auto cat = dynamic_cast<KMyMoneyCategory*>(haveWidget("asset-account")); 0265 if (cat && cat->isVisible()) { 0266 if (cat->currentText().isEmpty()) { 0267 MyMoneyAccount acc = MyMoneyFile::instance()->accountByName(i18n("%1 (Brokerage)", d->m_parent->account().name())); 0268 if (!acc.id().isEmpty()) { 0269 bool blocked = cat->signalsBlocked(); 0270 // block signals, so that the focus does not go crazy 0271 cat->blockSignals(true); 0272 cat->completion()->setSelected(acc.id()); 0273 cat->slotItemSelected(acc.id()); 0274 cat->blockSignals(blocked); 0275 } 0276 } 0277 } 0278 } 0279 0280 void Activity::setWidgetVisibility(const QStringList& widgetIds, bool visible) const 0281 { 0282 for (QStringList::const_iterator it_w = widgetIds.constBegin(); it_w != widgetIds.constEnd(); ++it_w) { 0283 auto w = haveWidget(*it_w); 0284 if (w) { 0285 // in case we hit a category with a split button, 0286 // we need to manipulate the enclosing QFrame 0287 auto cat = dynamic_cast<KMyMoneyCategory*>(w); 0288 if (cat && cat->splitButton()) { 0289 cat->parentWidget()->setVisible(visible); 0290 } else { 0291 w->setVisible(visible); 0292 } 0293 } 0294 } 0295 } 0296 0297 eDialogs::PriceMode Activity::priceMode() const 0298 { 0299 Q_D(const Activity); 0300 return d->m_parent->priceMode(); 0301 } 0302 0303 QString Activity::priceLabel() const 0304 { 0305 QString label; 0306 if (priceMode() == eDialogs::PriceMode::Price) { 0307 label = i18n("Price"); 0308 } else if (priceMode() == eDialogs::PriceMode::PricePerShare) { 0309 label = i18n("Price/share"); 0310 } else if (priceMode() == eDialogs::PriceMode::PricePerTransaction) { 0311 label = i18n("Transaction amount"); 0312 } 0313 return label; 0314 } 0315 0316 Buy::Buy(InvestTransactionEditor* editor) : 0317 Activity(editor) 0318 { 0319 } 0320 0321 Buy::~Buy() 0322 { 0323 } 0324 0325 eMyMoney::Split::InvestmentTransactionType Buy::type() const 0326 { 0327 return eMyMoney::Split::InvestmentTransactionType::BuyShares; 0328 } 0329 0330 void Buy::showWidgets() const 0331 { 0332 static const QStringList visibleWidgetIds = {"asset-account", "shares", "price", "total", "fee-account", "fee-amount", "interest-account", "interest-amount",}; 0333 setWidgetVisibility(visibleWidgetIds, true); 0334 0335 setLabelText("interest-amount-label", i18n("Interest")); 0336 setLabelText("interest-label", i18n("Interest")); 0337 setLabelText("fee-amount-label", i18n("Fees")); 0338 setLabelText("fee-label", i18n("Fees")); 0339 setLabelText("asset-label", i18n("Account")); 0340 setLabelText("shares-label", i18n("Shares")); 0341 setLabelText("price-label", priceLabel()); 0342 setLabelText("total-label", i18nc("Total value", "Total")); 0343 } 0344 0345 bool Buy::isComplete(QString& reason) const 0346 { 0347 auto rc = Activity::isComplete(reason); 0348 rc &= haveAssetAccount(); 0349 rc &= haveFees(true); 0350 rc &= haveShares(); 0351 rc &= havePrice(); 0352 0353 return rc; 0354 } 0355 0356 bool Buy::createTransaction(MyMoneyTransaction& t, MyMoneySplit& s0, MyMoneySplit& assetAccountSplit, QList<MyMoneySplit>& feeSplits, QList<MyMoneySplit>& m_feeSplits, QList<MyMoneySplit>& interestSplits, QList<MyMoneySplit>& m_interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency) 0357 { 0358 Q_D(Activity); 0359 Q_UNUSED(m_interestSplits); 0360 Q_UNUSED(security); 0361 Q_UNUSED(currency); 0362 0363 QString reason; 0364 if (!isComplete(reason)) 0365 return false; 0366 0367 auto sharesEdit = dynamic_cast<AmountEdit*>(haveWidget("shares")); 0368 auto priceEdit = dynamic_cast<AmountEdit*>(haveWidget("price")); 0369 0370 s0.setAction(eMyMoney::Split::InvestmentTransactionType::BuyShares); 0371 0372 MyMoneyMoney shares = s0.shares(); 0373 MyMoneyMoney price; 0374 if (!s0.shares().isZero()) 0375 price = (s0.value() / s0.shares()).reduce(); 0376 0377 if (sharesEdit && (!isMultiSelection() || !sharesEdit->value().isZero())) { 0378 shares = sharesEdit->value().abs(); 0379 s0.setShares(shares); 0380 s0.setValue((shares * price).convert(currency.smallestAccountFraction(), security.roundingMethod())); 0381 s0.setPrice(price); 0382 } 0383 if (priceEdit && (!isMultiSelection() || !priceEdit->value().isZero())) { 0384 price = priceEdit->value().abs(); 0385 if (priceMode() == eDialogs::PriceMode::PricePerTransaction) { 0386 s0.setValue(price.reduce()); 0387 if (!s0.shares().isZero()) 0388 s0.setPrice((price / s0.shares()).reduce()); 0389 } else { 0390 s0.setValue((shares * price).convert(currency.smallestAccountFraction(), security.roundingMethod())); 0391 s0.setPrice(price); 0392 } 0393 } 0394 0395 auto feeAccountWidget = dynamic_cast<KMyMoneyCategory*>(haveWidget("fee-account")); 0396 auto feeAmountWidget = dynamic_cast<AmountEdit*>(haveWidget("fee-amount")); 0397 if (!feeAccountWidget || !feeAmountWidget // 0398 || !createCategorySplits(t, feeAccountWidget, feeAmountWidget, MyMoneyMoney::ONE, feeSplits, m_feeSplits)) 0399 return false; 0400 0401 createAssetAccountSplit(assetAccountSplit, s0); 0402 0403 MyMoneyMoney total = sumSplits(s0, feeSplits, QList<MyMoneySplit>()); 0404 0405 // Clear any leftover value from previous Dividend. 0406 interestSplits.clear(); 0407 0408 assetAccountSplit.setValue(-total); 0409 0410 if (!d->m_parent->setupPrice(t, assetAccountSplit)) 0411 return false; 0412 0413 return true; 0414 } 0415 0416 Sell::Sell(InvestTransactionEditor* editor) : 0417 Activity(editor) 0418 { 0419 } 0420 0421 Sell::~Sell() 0422 { 0423 } 0424 0425 eMyMoney::Split::InvestmentTransactionType Sell::type() const 0426 { 0427 return eMyMoney::Split::InvestmentTransactionType::SellShares; 0428 } 0429 0430 void Sell::showWidgets() const 0431 { 0432 Q_D(const Activity); 0433 static const QStringList visibleWidgetIds = QStringList{"asset-account", "interest-amount", "fee-amount", "shares", "price", "total", "interest-account", "fee-account"}; 0434 setWidgetVisibility(visibleWidgetIds, true); 0435 0436 if (auto shareEdit = dynamic_cast<AmountEdit*>(haveWidget("shares"))) 0437 shareEdit->setPrecision(MyMoneyMoney::denomToPrec(d->m_parent->security().smallestAccountFraction())); 0438 0439 setLabelText("interest-amount-label", i18n("Interest")); 0440 setLabelText("interest-label", i18n("Interest")); 0441 setLabelText("fee-amount-label", i18n("Fees")); 0442 setLabelText("fee-label", i18n("Fees")); 0443 setLabelText("asset-label", i18n("Account")); 0444 setLabelText("shares-label", i18n("Shares")); 0445 setLabelText("price-label", priceLabel()); 0446 setLabelText("total-label", i18nc("Total value", "Total")); 0447 } 0448 0449 bool Sell::isComplete(QString& reason) const 0450 { 0451 Q_D(const Activity); 0452 0453 auto rc = Activity::isComplete(reason); 0454 rc &= haveFees(true); 0455 rc &= haveInterest(true); 0456 rc &= haveShares(); 0457 rc &= havePrice(); 0458 0459 // Allow a sell operation to be saved without specifying a brokerage 0460 // account, when the proceeds equal the fees. This will handle sales 0461 // made solely to cover annual account fees, where there is no money 0462 // transferred. 0463 if (rc) { 0464 if (!d->m_parent->totalAmount().isZero()) { 0465 rc &= haveAssetAccount(); 0466 } 0467 } 0468 return rc; 0469 } 0470 0471 bool Sell::createTransaction(MyMoneyTransaction& t, MyMoneySplit& s0, MyMoneySplit& assetAccountSplit, QList<MyMoneySplit>& feeSplits, QList<MyMoneySplit>& m_feeSplits, QList<MyMoneySplit>& interestSplits, QList<MyMoneySplit>& m_interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency) 0472 { 0473 Q_D(Activity); 0474 Q_UNUSED(m_interestSplits); 0475 Q_UNUSED(security); 0476 Q_UNUSED(currency); 0477 0478 QString reason; 0479 if (!isComplete(reason)) 0480 return false; 0481 0482 auto sharesEdit = dynamic_cast<AmountEdit*>(haveWidget("shares")); 0483 auto priceEdit = dynamic_cast<AmountEdit*>(haveWidget("price")); 0484 0485 s0.setAction(eMyMoney::Split::InvestmentTransactionType::BuyShares); 0486 0487 MyMoneyMoney shares = s0.shares(); 0488 MyMoneyMoney price; 0489 if (!s0.shares().isZero()) 0490 price = (s0.value() / s0.shares()).reduce(); 0491 0492 if (sharesEdit && (!isMultiSelection() || !sharesEdit->value().isZero())) { 0493 shares = -sharesEdit->value().abs(); 0494 s0.setShares(shares); 0495 s0.setValue((shares * price).convert(currency.smallestAccountFraction(), security.roundingMethod())); 0496 s0.setPrice(price); 0497 } 0498 if (priceEdit && (!isMultiSelection() || !priceEdit->value().isZero())) { 0499 price = priceEdit->value().abs(); 0500 if (priceMode() == eDialogs::PriceMode::PricePerTransaction) { 0501 price = -price; 0502 s0.setValue(price.reduce()); 0503 if (!s0.shares().isZero()) 0504 s0.setPrice((price / s0.shares()).reduce()); 0505 } else { 0506 s0.setValue((shares * price).convert(currency.smallestAccountFraction(), security.roundingMethod())); 0507 s0.setPrice(price); 0508 } 0509 } 0510 0511 auto feeAccountWidget = dynamic_cast<KMyMoneyCategory*>(haveWidget("fee-account")); 0512 auto feeAmountWidget = dynamic_cast<AmountEdit*>(haveWidget("fee-amount")); 0513 if (!feeAccountWidget || !feeAmountWidget // 0514 || !createCategorySplits(t, feeAccountWidget, feeAmountWidget, MyMoneyMoney::ONE, feeSplits, m_feeSplits)) 0515 return false; 0516 0517 auto interestAccountWidget = dynamic_cast<KMyMoneyCategory*>(haveWidget("interest-account")); 0518 auto interestAmountWidget = dynamic_cast<AmountEdit*>(haveWidget("interest-amount")); 0519 if (!interestAccountWidget || !interestAmountWidget // 0520 || !createCategorySplits(t, interestAccountWidget, interestAmountWidget, MyMoneyMoney::MINUS_ONE, interestSplits, m_interestSplits)) 0521 return false; 0522 0523 const auto total = sumSplits(s0, feeSplits, interestSplits); 0524 if (!total.isZero()) { 0525 createAssetAccountSplit(assetAccountSplit, s0); 0526 assetAccountSplit.setValue(-total); 0527 0528 if (!d->m_parent->setupPrice(t, assetAccountSplit)) 0529 return false; 0530 } 0531 0532 return true; 0533 } 0534 0535 Div::Div(InvestTransactionEditor* editor) : 0536 Activity(editor) 0537 { 0538 } 0539 0540 Div::~Div() 0541 { 0542 } 0543 0544 eMyMoney::Split::InvestmentTransactionType Div::type() const 0545 { 0546 return eMyMoney::Split::InvestmentTransactionType::Dividend; 0547 } 0548 0549 void Div::showWidgets() const 0550 { 0551 static const QStringList visibleWidgetIds = {"asset-account", "interest-amount", "fee-amount", "total", "interest-account", "fee-account"}; 0552 setWidgetVisibility(visibleWidgetIds, true); 0553 static const QStringList hiddenWidgetIds = {"shares", "price"}; 0554 setWidgetVisibility(hiddenWidgetIds, false); 0555 0556 setLabelText("interest-amount-label", i18n("Interest")); 0557 setLabelText("interest-label", i18n("Interest")); 0558 setLabelText("fee-amount-label", i18n("Fees")); 0559 setLabelText("fee-label", i18n("Fees")); 0560 setLabelText("asset-label", i18n("Account")); 0561 setLabelText("total-label", i18nc("Total value", "Total")); 0562 } 0563 0564 bool Div::isComplete(QString& reason) const 0565 { 0566 Q_UNUSED(reason) 0567 0568 auto rc = Activity::isComplete(reason); 0569 rc &= haveAssetAccount(); 0570 rc &= haveCategoryAndAmount("interest-account", QString(), false); 0571 rc &= haveInterest(false); 0572 return rc; 0573 } 0574 0575 bool Div::createTransaction(MyMoneyTransaction& t, MyMoneySplit& s0, MyMoneySplit& assetAccountSplit, QList<MyMoneySplit>& feeSplits, QList<MyMoneySplit>& m_feeSplits, QList<MyMoneySplit>& interestSplits, QList<MyMoneySplit>& m_interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency) 0576 { 0577 Q_D(Activity); 0578 Q_UNUSED(m_feeSplits); 0579 Q_UNUSED(security); 0580 Q_UNUSED(currency); 0581 0582 QString reason; 0583 if (!isComplete(reason)) 0584 return false; 0585 0586 s0.setAction(eMyMoney::Split::InvestmentTransactionType::Dividend); 0587 0588 // for dividends, we only use the stock split as a marker 0589 MyMoneyMoney shares; 0590 s0.setShares(shares); 0591 s0.setValue(shares); 0592 s0.setPrice(MyMoneyMoney::ONE); 0593 0594 auto feeAccountWidget = dynamic_cast<KMyMoneyCategory*>(haveWidget("fee-account")); 0595 auto feeAmountWidget = dynamic_cast<AmountEdit*>(haveWidget("fee-amount")); 0596 if (!feeAccountWidget || !feeAmountWidget // 0597 || !createCategorySplits(t, feeAccountWidget, feeAmountWidget, MyMoneyMoney::ONE, feeSplits, m_feeSplits)) 0598 return false; 0599 0600 auto interestAccountWidget = dynamic_cast<KMyMoneyCategory*>(haveWidget("interest-account")); 0601 auto interestAmountWidget = dynamic_cast<AmountEdit*>(haveWidget("interest-amount")); 0602 if (!interestAccountWidget || !interestAmountWidget // 0603 || !createCategorySplits(t, interestAccountWidget, interestAmountWidget, MyMoneyMoney::MINUS_ONE, interestSplits, m_interestSplits)) 0604 return false; 0605 0606 createAssetAccountSplit(assetAccountSplit, s0); 0607 0608 MyMoneyMoney total = sumSplits(s0, feeSplits, interestSplits); 0609 assetAccountSplit.setValue(-total); 0610 0611 if (!d->m_parent->setupPrice(t, assetAccountSplit)) 0612 return false; 0613 0614 return true; 0615 } 0616 0617 Reinvest::Reinvest(InvestTransactionEditor* editor) : 0618 Activity(editor) 0619 { 0620 } 0621 0622 Reinvest::~Reinvest() 0623 { 0624 } 0625 0626 eMyMoney::Split::InvestmentTransactionType Reinvest::type() const 0627 { 0628 return eMyMoney::Split::InvestmentTransactionType::ReinvestDividend; 0629 } 0630 0631 void Reinvest::showWidgets() const 0632 { 0633 Q_D(const Activity); 0634 static const QStringList visibleWidgetIds = QStringList{"price", "fee-account", "interest-account"}; 0635 setWidgetVisibility(visibleWidgetIds, true); 0636 0637 if (auto shareEdit = dynamic_cast<AmountEdit*>(haveWidget("shares"))) { 0638 shareEdit->show(); 0639 shareEdit->setPrecision(MyMoneyMoney::denomToPrec(d->m_parent->security().smallestAccountFraction())); 0640 } 0641 0642 setLabelText("interest-amount-label", i18n("Interest")); 0643 setLabelText("interest-label", i18n("Interest")); 0644 setLabelText("fee-amount-label", i18n("Fees")); 0645 setLabelText("fee-label", i18n("Fees")); 0646 setLabelText("interest-label", i18n("Interest")); 0647 setLabelText("shares-label", i18n("Shares")); 0648 setLabelText("price-label", priceLabel()); 0649 setLabelText("total-label", i18nc("Total value", "Total")); 0650 } 0651 0652 bool Reinvest::isComplete(QString& reason) const 0653 { 0654 auto rc = Activity::isComplete(reason); 0655 rc &= haveCategoryAndAmount("interest-account", QString(), false); 0656 rc &= haveFees(true); 0657 rc &= haveShares(); 0658 rc &= havePrice(); 0659 return rc; 0660 } 0661 0662 bool Reinvest::createTransaction(MyMoneyTransaction& t, MyMoneySplit& s0, MyMoneySplit& assetAccountSplit, QList<MyMoneySplit>& feeSplits, QList<MyMoneySplit>& m_feeSplits, QList<MyMoneySplit>& interestSplits, QList<MyMoneySplit>& m_interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency) 0663 { 0664 Q_D(Activity); 0665 Q_UNUSED(assetAccountSplit); 0666 Q_UNUSED(security); 0667 Q_UNUSED(currency); 0668 0669 QString reason; 0670 if (!isComplete(reason)) 0671 return false; 0672 0673 auto sharesEdit = dynamic_cast<AmountEdit*>(haveWidget("shares")); 0674 auto priceEdit = dynamic_cast<AmountEdit*>(haveWidget("price")); 0675 0676 s0.setAction(eMyMoney::Split::InvestmentTransactionType::ReinvestDividend); 0677 0678 MyMoneyMoney shares = s0.shares(); 0679 MyMoneyMoney price; 0680 if (!s0.shares().isZero()) 0681 price = (s0.value() / s0.shares()).reduce(); 0682 0683 if (sharesEdit && (!isMultiSelection() || !sharesEdit->value().isZero())) { 0684 shares = sharesEdit->value().abs(); 0685 s0.setShares(shares); 0686 s0.setValue((shares * price).convert(currency.smallestAccountFraction(), security.roundingMethod())); 0687 s0.setPrice(price); 0688 } 0689 if (priceEdit && (!isMultiSelection() || !priceEdit->value().isZero())) { 0690 price = priceEdit->value().abs(); 0691 if (priceMode() == eDialogs::PriceMode::PricePerTransaction) { 0692 s0.setValue(price.reduce()); 0693 if (!s0.shares().isZero()) 0694 s0.setPrice((price / s0.shares()).reduce()); 0695 } else { 0696 s0.setValue((shares * price).convert(currency.smallestAccountFraction(), security.roundingMethod())); 0697 s0.setPrice(price); 0698 } 0699 } 0700 0701 auto feeAccountWidget = dynamic_cast<KMyMoneyCategory*>(haveWidget("fee-account")); 0702 auto feeAmountWidget = dynamic_cast<AmountEdit*>(haveWidget("fee-amount")); 0703 if (feeAmountWidget && feeAccountWidget) { 0704 if (!createCategorySplits(t, feeAccountWidget, feeAmountWidget, MyMoneyMoney::ONE, feeSplits, m_feeSplits)) 0705 return false; 0706 } 0707 0708 auto interestAccountWidget = dynamic_cast<KMyMoneyCategory*>(haveWidget("interest-account")); 0709 auto interestAmountWidget = dynamic_cast<AmountEdit*>(haveWidget("interest-amount")); 0710 if (!interestAccountWidget || !interestAmountWidget // 0711 || !createCategorySplits(t, interestAccountWidget, interestAmountWidget, MyMoneyMoney::MINUS_ONE, interestSplits, m_interestSplits)) 0712 return false; 0713 0714 if (interestSplits.count() != 1) { 0715 qDebug("more or less than one interest split in Reinvest::createTransaction. Not created."); 0716 return false; 0717 } 0718 assetAccountSplit.setAccountId(QString()); 0719 0720 MyMoneySplit& s1 = interestSplits[0]; 0721 0722 MyMoneyMoney total = sumSplits(s0, feeSplits, QList<MyMoneySplit>()); 0723 0724 s1.setValue(-total); 0725 0726 if (!d->m_parent->setupPrice(t, s1)) 0727 return false; 0728 0729 return true; 0730 } 0731 0732 Add::Add(InvestTransactionEditor* editor) : 0733 Activity(editor) 0734 { 0735 } 0736 0737 Add::~Add() 0738 { 0739 } 0740 0741 eMyMoney::Split::InvestmentTransactionType Add::type() const 0742 { 0743 return eMyMoney::Split::InvestmentTransactionType::AddShares; 0744 } 0745 0746 void Add::showWidgets() const 0747 { 0748 Q_D(const Activity); 0749 if (auto shareEdit = dynamic_cast<AmountEdit*>(haveWidget("shares"))) { 0750 shareEdit->show(); 0751 shareEdit->setPrecision(MyMoneyMoney::denomToPrec(d->m_parent->security().smallestAccountFraction())); 0752 } 0753 0754 setLabelText("shares-label", i18n("Shares")); 0755 } 0756 0757 bool Add::isComplete(QString& reason) const 0758 { 0759 auto rc = Activity::isComplete(reason); 0760 rc &= haveShares(); 0761 return rc; 0762 } 0763 0764 bool Add::createTransaction(MyMoneyTransaction& t, MyMoneySplit& s0, MyMoneySplit& assetAccountSplit, QList<MyMoneySplit>& feeSplits, QList<MyMoneySplit>& m_feeSplits, QList<MyMoneySplit>& interestSplits, QList<MyMoneySplit>& m_interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency) 0765 { 0766 Q_UNUSED(t); 0767 Q_UNUSED(assetAccountSplit); 0768 Q_UNUSED(m_feeSplits); 0769 Q_UNUSED(m_interestSplits); 0770 Q_UNUSED(security); 0771 Q_UNUSED(currency); 0772 0773 QString reason; 0774 if (!isComplete(reason)) 0775 return false; 0776 0777 auto sharesEdit = dynamic_cast<AmountEdit*>(haveWidget("shares")); 0778 0779 s0.setAction(eMyMoney::Split::InvestmentTransactionType::AddShares); 0780 if (sharesEdit) 0781 s0.setShares(sharesEdit->value().abs()); 0782 s0.setValue(MyMoneyMoney()); 0783 s0.setPrice(MyMoneyMoney()); 0784 0785 assetAccountSplit.setValue(MyMoneyMoney());// Clear any leftover value from previous Dividend. 0786 0787 feeSplits.clear(); 0788 interestSplits.clear(); 0789 0790 return true; 0791 } 0792 0793 Remove::Remove(InvestTransactionEditor* editor) : 0794 Activity(editor) 0795 { 0796 } 0797 0798 Remove::~Remove() 0799 { 0800 } 0801 0802 eMyMoney::Split::InvestmentTransactionType Remove::type() const 0803 { 0804 return eMyMoney::Split::InvestmentTransactionType::RemoveShares; 0805 } 0806 0807 void Remove::showWidgets() const 0808 { 0809 Q_D(const Activity); 0810 if (auto shareEdit = dynamic_cast<AmountEdit*>(haveWidget("shares"))) { 0811 shareEdit->show(); 0812 shareEdit->setPrecision(MyMoneyMoney::denomToPrec(d->m_parent->security().smallestAccountFraction())); 0813 } 0814 setLabelText("shares-label", i18n("Shares")); 0815 } 0816 0817 bool Remove::isComplete(QString& reason) const 0818 { 0819 auto rc = Activity::isComplete(reason); 0820 rc &= haveShares(); 0821 return rc; 0822 } 0823 0824 bool Remove::createTransaction(MyMoneyTransaction& t, MyMoneySplit& s0, MyMoneySplit& assetAccountSplit, QList<MyMoneySplit>& feeSplits, QList<MyMoneySplit>& m_feeSplits, QList<MyMoneySplit>& interestSplits, QList<MyMoneySplit>& m_interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency) 0825 { 0826 Q_UNUSED(t); 0827 Q_UNUSED(assetAccountSplit); 0828 Q_UNUSED(m_feeSplits); 0829 Q_UNUSED(m_interestSplits); 0830 Q_UNUSED(security); 0831 Q_UNUSED(currency); 0832 0833 QString reason; 0834 if (!isComplete(reason)) 0835 return false; 0836 0837 s0.setAction(eMyMoney::Split::InvestmentTransactionType::AddShares); 0838 if (auto sharesEdit = dynamic_cast<AmountEdit*>(haveWidget("shares"))) 0839 s0.setShares(-(sharesEdit->value().abs())); 0840 s0.setValue(MyMoneyMoney()); 0841 s0.setPrice(MyMoneyMoney()); 0842 0843 assetAccountSplit.setValue(MyMoneyMoney());// Clear any leftover value from previous Dividend. 0844 0845 feeSplits.clear(); 0846 interestSplits.clear(); 0847 0848 return true; 0849 } 0850 0851 Invest::Split::Split(InvestTransactionEditor* editor) : 0852 Activity(editor) 0853 { 0854 } 0855 0856 Invest::Split::~Split() 0857 { 0858 } 0859 0860 eMyMoney::Split::InvestmentTransactionType Invest::Split::type() const 0861 { 0862 return eMyMoney::Split::InvestmentTransactionType::SplitShares; 0863 } 0864 0865 void Invest::Split::showWidgets() const 0866 { 0867 // TODO do we need a special split ratio widget? 0868 // TODO maybe yes, currently the precision is the one of the fraction and might differ from it 0869 if (auto shareEdit = dynamic_cast<AmountEdit*>(haveWidget("shares"))) { 0870 shareEdit->show(); 0871 shareEdit->setPrecision(-1); 0872 } 0873 setLabelText("shares-label", i18n("Ratio 1/")); 0874 } 0875 0876 bool Invest::Split::isComplete(QString& reason) const 0877 { 0878 auto rc = Activity::isComplete(reason); 0879 rc &= haveShares(); 0880 return rc; 0881 } 0882 0883 bool Invest::Split::createTransaction(MyMoneyTransaction& t, MyMoneySplit& s0, MyMoneySplit& assetAccountSplit, QList<MyMoneySplit>& feeSplits, QList<MyMoneySplit>& m_feeSplits, QList<MyMoneySplit>& interestSplits, QList<MyMoneySplit>& m_interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency) 0884 { 0885 Q_UNUSED(t); 0886 Q_UNUSED(assetAccountSplit); 0887 Q_UNUSED(m_feeSplits); 0888 Q_UNUSED(m_interestSplits); 0889 Q_UNUSED(security); 0890 Q_UNUSED(currency); 0891 0892 auto sharesEdit = dynamic_cast<AmountEdit*>(haveWidget("shares")); 0893 0894 s0.setAction(eMyMoney::Split::InvestmentTransactionType::SplitShares); 0895 if (sharesEdit) 0896 s0.setShares(sharesEdit->value().abs()); 0897 s0.setValue(MyMoneyMoney()); 0898 s0.setPrice(MyMoneyMoney()); 0899 0900 feeSplits.clear(); 0901 interestSplits.clear(); 0902 0903 return true; 0904 } 0905 0906 IntInc::IntInc(InvestTransactionEditor* editor) : 0907 Activity(editor) 0908 { 0909 } 0910 0911 IntInc::~IntInc() 0912 { 0913 } 0914 0915 eMyMoney::Split::InvestmentTransactionType IntInc::type() const 0916 { 0917 return eMyMoney::Split::InvestmentTransactionType::InterestIncome; 0918 } 0919 0920 void IntInc::showWidgets() const 0921 { 0922 static const QStringList visibleWidgetIds = {"asset-account", "interest-amount", "total", "interest-account", "fee-amount", "fee-account"}; 0923 setWidgetVisibility(visibleWidgetIds, true); 0924 static const QStringList hiddenWidgetIds = {"shares", "price"}; 0925 setWidgetVisibility(hiddenWidgetIds, false); 0926 0927 setLabelText("interest-amount-label", i18n("Interest")); 0928 setLabelText("interest-label", i18n("Interest")); 0929 setLabelText("fee-amount-label", i18n("Fees")); 0930 setLabelText("fee-label", i18n("Fees")); 0931 setLabelText("asset-label", i18n("Account")); 0932 setLabelText("total-label", i18nc("Total value", "Total")); 0933 } 0934 0935 bool IntInc::isComplete(QString& reason) const 0936 { 0937 Q_UNUSED(reason) 0938 0939 auto rc = Activity::isComplete(reason); 0940 rc &= haveAssetAccount(); 0941 rc &= haveCategoryAndAmount("interest-account", QString(), false); 0942 rc &= haveInterest(false); 0943 return rc; 0944 } 0945 0946 bool IntInc::createTransaction(MyMoneyTransaction& t, MyMoneySplit& s0, MyMoneySplit& assetAccountSplit, QList<MyMoneySplit>& feeSplits, QList<MyMoneySplit>& m_feeSplits, QList<MyMoneySplit>& interestSplits, QList<MyMoneySplit>& m_interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency) 0947 { 0948 Q_D(Activity); 0949 Q_UNUSED(security); 0950 Q_UNUSED(currency); 0951 0952 QString reason; 0953 if (!isComplete(reason)) 0954 return false; 0955 0956 s0.setAction(eMyMoney::Split::InvestmentTransactionType::InterestIncome); 0957 0958 // for dividends, we only use the stock split as a marker 0959 MyMoneyMoney shares; 0960 s0.setShares(shares); 0961 s0.setValue(shares); 0962 s0.setPrice(MyMoneyMoney::ONE); 0963 0964 auto feeAccountWidget = dynamic_cast<KMyMoneyCategory*>(haveWidget("fee-account")); 0965 auto feeAmountWidget = dynamic_cast<AmountEdit*>(haveWidget("fee-amount")); 0966 if (!feeAccountWidget || !feeAmountWidget // 0967 || !createCategorySplits(t, feeAccountWidget, feeAmountWidget, MyMoneyMoney::ONE, feeSplits, m_feeSplits)) 0968 return false; 0969 0970 auto interestAccountWidget = dynamic_cast<KMyMoneyCategory*>(haveWidget("interest-account")); 0971 auto interestAmountWidget = dynamic_cast<AmountEdit*>(haveWidget("interest-amount")); 0972 if (!interestAccountWidget || !interestAmountWidget // 0973 || !createCategorySplits(t, interestAccountWidget, interestAmountWidget, MyMoneyMoney::MINUS_ONE, interestSplits, m_interestSplits)) 0974 return false; 0975 0976 createAssetAccountSplit(assetAccountSplit, s0); 0977 0978 MyMoneyMoney total = sumSplits(s0, feeSplits, interestSplits); 0979 assetAccountSplit.setValue(-total); 0980 0981 if (!d->m_parent->setupPrice(t, assetAccountSplit)) 0982 return false; 0983 0984 return true; 0985 }