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 }