File indexing completed on 2024-05-12 05:07:35
0001 /* 0002 SPDX-FileCopyrightText: 2020 Thomas Baumgart <tbaumgart@kde.org> 0003 SPDX-License-Identifier: GPL-2.0-or-later 0004 */ 0005 0006 #include "investactivities.h" 0007 0008 // ---------------------------------------------------------------------------- 0009 // QT Includes 0010 0011 #include <QLabel> 0012 #include <QList> 0013 0014 // ---------------------------------------------------------------------------- 0015 // KDE Includes 0016 0017 #include <KLocalizedString> 0018 0019 // ---------------------------------------------------------------------------- 0020 // Project Includes 0021 0022 #include "investtransactioneditor.h" 0023 #include "mymoneymoney.h" 0024 #include "kmymoneyaccountcombo.h" 0025 #include "amountedit.h" 0026 #include "kmymoneyaccountselector.h" 0027 #include "kmymoneycompletion.h" 0028 #include "kmymoneysettings.h" 0029 #include "mymoneyfile.h" 0030 #include "mymoneysplit.h" 0031 #include "mymoneyaccount.h" 0032 #include "mymoneysecurity.h" 0033 #include "dialogenums.h" 0034 #include "mymoneyenums.h" 0035 #include "widgethintframe.h" 0036 #include "splitmodel.h" 0037 0038 using namespace Invest; 0039 0040 class Invest::ActivityPrivate 0041 { 0042 Q_DISABLE_COPY(ActivityPrivate) 0043 0044 public: 0045 ActivityPrivate(InvestTransactionEditor* parent) 0046 : editor(parent) 0047 { 0048 } 0049 0050 template <typename T> 0051 inline T* haveWidget(const QString &aName) const 0052 { 0053 return editor->findChild<T*>(aName); 0054 } 0055 0056 template <typename T> 0057 inline T* haveVisibleWidget(const QString &aName) const 0058 { 0059 auto widget = editor->findChild<T*>(aName); 0060 if (!widget) { 0061 qDebug() << "Widget with name" << aName << "not found"; 0062 } 0063 if (widget && widget->isVisible()) 0064 return widget; 0065 return nullptr; 0066 } 0067 0068 void createAssetAccountSplit(MyMoneySplit& split, const MyMoneySplit& stockSplit) const 0069 { 0070 auto cat = haveWidget<KMyMoneyAccountCombo>("assetAccountCombo"); 0071 if (cat) { 0072 auto categoryId = cat->getSelected(); 0073 split.setAccountId(categoryId); 0074 } 0075 split.setMemo(stockSplit.memo()); 0076 } 0077 0078 MyMoneyMoney sumSplits(const MyMoneySplit& s0, const SplitModel* feesModel, const SplitModel* interestModel) const 0079 { 0080 auto total = s0.value(); 0081 0082 if (feesModel) { 0083 total += feesModel->valueSum(); 0084 } 0085 if (interestModel) { 0086 total += interestModel->valueSum(); 0087 } 0088 return total; 0089 } 0090 0091 eDialogs::PriceMode priceMode() const 0092 { 0093 eDialogs::PriceMode mode = eDialogs::PriceMode::Price; 0094 auto sec = haveWidget<QComboBox>("securityAccountCombo"); 0095 0096 QString accId; 0097 if (sec && !sec->currentText().isEmpty()) { 0098 const auto idx = sec->model()->index(sec->currentIndex(), 0); 0099 accId = idx.data(eMyMoney::Model::IdRole).toString(); 0100 } 0101 while (!accId.isEmpty() && mode == eDialogs::PriceMode::Price) { 0102 auto acc = MyMoneyFile::instance()->account(accId); 0103 if (acc.value("priceMode").isEmpty()) 0104 accId = acc.parentAccountId(); 0105 else 0106 mode = static_cast<eDialogs::PriceMode>(acc.value("priceMode").toInt()); 0107 } 0108 0109 // if mode is still <Price> then use that 0110 if (mode == eDialogs::PriceMode::Price) { 0111 mode = eDialogs::PriceMode::PricePerShare; 0112 } 0113 return mode; 0114 } 0115 0116 MyMoneyMoney valueAllShares() const 0117 { 0118 const auto shares = haveVisibleWidget<AmountEdit>("sharesAmountEdit"); 0119 const auto price = haveVisibleWidget<AmountEdit>("priceAmountEdit"); 0120 MyMoneyMoney result; 0121 if (shares && price) { 0122 if (priceMode() == eDialogs::PriceMode::PricePerShare) { 0123 result = shares->value() * price->value(); 0124 } else { 0125 result = price->value(); 0126 } 0127 } 0128 return result; 0129 } 0130 0131 InvestTransactionEditor* editor; 0132 QString actionString; 0133 }; 0134 0135 Activity::Activity(InvestTransactionEditor* editor, const QString& action) 0136 : d_ptr(new ActivityPrivate(editor)) 0137 { 0138 Q_D(Activity); 0139 d->actionString = action; 0140 } 0141 0142 Activity::~Activity() 0143 { 0144 Q_D(Activity); 0145 delete d; 0146 } 0147 0148 void Activity::setupWidgets(const QStringList& activityWidgets) const 0149 { 0150 static const QStringList dynamicWidgetNames = { 0151 // clang-format off 0152 "sharesLabel", "sharesAmountEdit", 0153 "assetAccountLabel", "assetAccountCombo", 0154 "priceLabel", "priceAmountEdit", 0155 "feesLabel", "feesCombo", "feesAmountLabel", "feesAmountEdit", 0156 "interestLabel", "interestCombo", "interestAmountLabel", "interestAmountEdit", 0157 "totalLabel", "totalAmountEdit", 0158 // clang-format on 0159 }; 0160 0161 setWidgetVisibility(dynamicWidgetNames, false); 0162 0163 setLabelText("priceLabel", priceLabelText()); 0164 setLabelText("sharesLabel", sharesLabelText()); 0165 0166 static const QStringList standardWidgetNames = { 0167 // clang-format off 0168 "activityLabel", "activityCombo", 0169 "dateLabel", "dateEdit", 0170 "securityLabel", "securityAccountCombo", 0171 0172 // the ones in between are dynamically handled 0173 0174 "memoLabel", "memoEdit", 0175 "statusLabel", "statusCombo", 0176 "enterButton", "cancelButton", 0177 // clang-format on 0178 }; 0179 0180 setWidgetVisibility(standardWidgetNames, true); 0181 setWidgetVisibility(activityWidgets, true); 0182 } 0183 0184 bool Activity::haveCategoryAndAmount(const QString& categoryWidget, const QString& amountWidget, fieldRequired_t optional) const 0185 { 0186 Q_D(const Activity); 0187 const auto cat = d->haveVisibleWidget<KMyMoneyAccountCombo>(categoryWidget); 0188 const auto amount = d->haveVisibleWidget<AmountEdit>(amountWidget); 0189 auto rc = true; 0190 0191 if (cat && amount) { 0192 switch(optional) { 0193 case Unused: 0194 break; 0195 case Optional: 0196 // both must be filled or empty to be OK 0197 rc = cat->currentText().isEmpty() == amount->value().isZero(); 0198 break; 0199 case Mandatory: 0200 // both must be filled to be OK 0201 rc = !(cat->currentText().isEmpty() || amount->value().isZero()); 0202 break; 0203 } 0204 } 0205 return rc; 0206 } 0207 0208 bool Activity::haveFees(fieldRequired_t optional) const 0209 { 0210 return haveCategoryAndAmount("feesCombo", "feesAmountEdit", optional); 0211 } 0212 0213 bool Activity::haveInterest(fieldRequired_t optional) const 0214 { 0215 return haveCategoryAndAmount("interestCombo", "interestAmountEdit", optional); 0216 } 0217 0218 MyMoneyMoney Activity::totalAmount(const MyMoneySplit& stockSplit, const SplitModel* feesModel, const SplitModel* interestModel) const 0219 { 0220 auto result = stockSplit.value(); 0221 0222 if (feesModel && (feesRequired() != Unused)) { 0223 result += feesModel->valueSum(); 0224 } 0225 0226 if (interestModel && (interestRequired() != Unused)) { 0227 result += interestModel->valueSum(); 0228 } 0229 0230 return result; 0231 } 0232 0233 void Activity::setLabelText(const QString& idx, const QString& txt) const 0234 { 0235 Q_D(const Activity); 0236 auto w = d->haveWidget<QLabel>(idx); 0237 if (w) { 0238 w->setText(txt); 0239 } 0240 } 0241 0242 void Activity::setWidgetVisibility(const QStringList& widgetIds, bool visible) const 0243 { 0244 Q_D(const Activity); 0245 for (const auto& name : qAsConst(widgetIds)) { 0246 auto w = d->haveWidget<QWidget>(name); 0247 if (w) { 0248 w->setVisible(visible); 0249 } else { 0250 qCritical() << "Activity::setWidgetVisibility unknown widget" << name; 0251 } 0252 } 0253 } 0254 0255 QString Invest::Activity::sharesLabelText() const 0256 { 0257 return i18nc("@label:textbox", "Shares"); 0258 } 0259 0260 QString Activity::priceLabelText() const 0261 { 0262 Q_D(const Activity); 0263 switch (d->priceMode()) { 0264 default: 0265 case eDialogs::PriceMode::Price: 0266 break; 0267 case eDialogs::PriceMode::PricePerShare: 0268 return i18nc("@label:textbox", "Price/share"); 0269 case eDialogs::PriceMode::PricePerTransaction: 0270 return i18nc("@label:textbox", "Transaction amount"); 0271 } 0272 return i18nc("@label:textbox", "Price"); 0273 } 0274 0275 void Activity::loadPriceWidget(const MyMoneySplit & split) 0276 { 0277 Q_D(const Activity); 0278 auto priceEdit = d->haveWidget<AmountEdit>("priceAmountEdit"); 0279 0280 if (priceEdit && !split.accountId().isEmpty()) { 0281 const auto account = MyMoneyFile::instance()->account(split.accountId()); 0282 const auto currency = MyMoneyFile::instance()->currency(account.tradingCurrencyId()); 0283 priceEdit->setCommodity(currency); 0284 if (d->priceMode() == eDialogs::PriceMode::PricePerTransaction) { 0285 priceEdit->setValue(split.value()); 0286 } else { 0287 priceEdit->setValue(split.price()); 0288 } 0289 } 0290 } 0291 0292 MyMoneyMoney Activity::sharesFactor() const 0293 { 0294 return MyMoneyMoney::ONE; 0295 } 0296 0297 MyMoneyMoney Activity::feesFactor() const 0298 { 0299 return MyMoneyMoney::ONE; 0300 } 0301 0302 MyMoneyMoney Activity::interestFactor() const 0303 { 0304 return MyMoneyMoney::MINUS_ONE; 0305 } 0306 0307 QString Activity::actionString() const 0308 { 0309 Q_D(const Activity); 0310 return d->actionString; 0311 } 0312 0313 MyMoneyMoney Activity::valueAllShares() const 0314 { 0315 Q_D(const Activity); 0316 return d->valueAllShares(); 0317 } 0318 0319 eDialogs::PriceMode Activity::priceMode() const 0320 { 0321 Q_D(const Activity); 0322 return d->priceMode(); 0323 } 0324 0325 Buy::Buy(InvestTransactionEditor* editor) 0326 : Activity(editor, QLatin1String("Buy")) 0327 { 0328 } 0329 0330 Buy::~Buy() 0331 { 0332 } 0333 0334 eMyMoney::Split::InvestmentTransactionType Buy::type() const 0335 { 0336 return eMyMoney::Split::InvestmentTransactionType::BuyShares; 0337 } 0338 0339 void Buy::showWidgets() const 0340 { 0341 static const QStringList activityWidgets = { 0342 // clang-format off 0343 "sharesLabel", "sharesAmountEdit", 0344 "assetAccountLabel", "assetAccountCombo", 0345 "priceLabel", "priceAmountEdit", 0346 "feesLabel", "feesCombo", "feesAmountLabel", "feesAmountEdit", 0347 "interestLabel", "interestCombo", "interestAmountLabel", "interestAmountEdit", 0348 "totalLabel", "totalAmountEdit", 0349 // clang-format on 0350 }; 0351 0352 setupWidgets(activityWidgets); 0353 } 0354 0355 Sell::Sell(InvestTransactionEditor* editor) 0356 : Activity(editor, QLatin1String("Buy")) 0357 { 0358 } 0359 0360 Sell::~Sell() 0361 { 0362 } 0363 0364 eMyMoney::Split::InvestmentTransactionType Sell::type() const 0365 { 0366 return eMyMoney::Split::InvestmentTransactionType::SellShares; 0367 } 0368 0369 void Sell::showWidgets() const 0370 { 0371 static const QStringList activityWidgets = { 0372 // clang-format off 0373 "sharesLabel", "sharesAmountEdit", 0374 "assetAccountLabel", "assetAccountCombo", 0375 "priceLabel", "priceAmountEdit", 0376 "feesLabel", "feesCombo", "feesAmountLabel", "feesAmountEdit", 0377 "interestLabel", "interestCombo", "interestAmountLabel", "interestAmountEdit", 0378 "totalLabel", "totalAmountEdit", 0379 // clang-format on 0380 }; 0381 0382 setupWidgets(activityWidgets); 0383 } 0384 0385 Invest::Activity::fieldRequired_t Invest::Sell::assetAccountRequired() const 0386 { 0387 Q_D(const Activity); 0388 return d->editor->totalAmount().isZero() ? Unused : Mandatory; 0389 } 0390 0391 MyMoneyMoney Sell::sharesFactor() const 0392 { 0393 return MyMoneyMoney::MINUS_ONE; 0394 } 0395 0396 Div::Div(InvestTransactionEditor* editor) 0397 : Activity(editor, QLatin1String("Dividend")) 0398 { 0399 } 0400 0401 Div::~Div() 0402 { 0403 } 0404 0405 eMyMoney::Split::InvestmentTransactionType Div::type() const 0406 { 0407 return eMyMoney::Split::InvestmentTransactionType::Dividend; 0408 } 0409 0410 void Div::showWidgets() const 0411 { 0412 static const QStringList activityWidgets = { 0413 // clang-format off 0414 "assetAccountLabel", "assetAccountCombo", 0415 "feesLabel", "feesCombo", "feesAmountLabel", "feesAmountEdit", 0416 "interestLabel", "interestCombo", "interestAmountLabel", "interestAmountEdit", 0417 "totalLabel", "totalAmountEdit", 0418 // clang-format on 0419 }; 0420 0421 setupWidgets(activityWidgets); 0422 } 0423 0424 Invest::Yield::Yield(InvestTransactionEditor* editor) 0425 : Activity(editor, QLatin1String("Yield")) 0426 { 0427 } 0428 0429 Invest::Yield::~Yield() 0430 { 0431 } 0432 0433 eMyMoney::Split::InvestmentTransactionType Invest::Yield::type() const 0434 { 0435 return eMyMoney::Split::InvestmentTransactionType::Yield; 0436 } 0437 0438 void Yield::showWidgets() const 0439 { 0440 static const QStringList activityWidgets = { 0441 // clang-format off 0442 "assetAccountLabel", "assetAccountCombo", 0443 "feesLabel", "feesCombo", "feesAmountLabel", "feesAmountEdit", 0444 "interestLabel", "interestCombo", "interestAmountLabel", "interestAmountEdit", 0445 "totalLabel", "totalAmountEdit", 0446 // clang-format on 0447 }; 0448 0449 setupWidgets(activityWidgets); 0450 } 0451 0452 Reinvest::Reinvest(InvestTransactionEditor* editor) 0453 : Activity(editor, QLatin1String("Reinvest")) 0454 { 0455 } 0456 0457 Reinvest::~Reinvest() 0458 { 0459 } 0460 0461 eMyMoney::Split::InvestmentTransactionType Reinvest::type() const 0462 { 0463 return eMyMoney::Split::InvestmentTransactionType::ReinvestDividend; 0464 } 0465 0466 void Reinvest::showWidgets() const 0467 { 0468 static const QStringList activityWidgets = { 0469 // clang-format off 0470 "sharesLabel", "sharesAmountEdit", 0471 "assetAccountLabel", "assetAccountCombo", 0472 "priceLabel", "priceAmountEdit", 0473 "feesLabel", "feesCombo", "feesAmountLabel", "feesAmountEdit", 0474 "interestLabel", "interestCombo", "interestAmountLabel", "interestAmountEdit", 0475 "totalLabel", "totalAmountEdit", 0476 // clang-format on 0477 }; 0478 0479 setupWidgets(activityWidgets); 0480 } 0481 0482 0483 MyMoneyMoney Invest::Reinvest::totalAmount(const MyMoneySplit & stockSplit, const SplitModel * feesModel, const SplitModel * interestModel) const 0484 { 0485 Q_UNUSED(stockSplit) 0486 Q_UNUSED(feesModel) 0487 Q_UNUSED(interestModel) 0488 0489 return {}; 0490 } 0491 0492 Add::Add(InvestTransactionEditor* editor) 0493 : Activity(editor, QLatin1String("Add")) 0494 { 0495 } 0496 0497 Add::~Add() 0498 { 0499 } 0500 0501 eMyMoney::Split::InvestmentTransactionType Add::type() const 0502 { 0503 return eMyMoney::Split::InvestmentTransactionType::AddShares; 0504 } 0505 0506 void Add::showWidgets() const 0507 { 0508 static const QStringList activityWidgets = { 0509 // clang-format off 0510 "sharesLabel", "sharesAmountEdit", 0511 // clang-format on 0512 }; 0513 0514 setupWidgets(activityWidgets); 0515 } 0516 0517 0518 void Add::adjustStockSplit(MyMoneySplit& stockSplit) 0519 { 0520 stockSplit.setValue(MyMoneyMoney()); 0521 stockSplit.setPrice(MyMoneyMoney()); 0522 } 0523 0524 MyMoneyMoney Invest::Add::totalAmount(const MyMoneySplit & stockSplit, const SplitModel * feesModel, const SplitModel * interestModel) const 0525 { 0526 Q_UNUSED(stockSplit) 0527 Q_UNUSED(feesModel) 0528 Q_UNUSED(interestModel) 0529 0530 return {}; 0531 } 0532 0533 Remove::Remove(InvestTransactionEditor* editor) 0534 : Activity(editor, QLatin1String("Add")) 0535 { 0536 } 0537 0538 Remove::~Remove() 0539 { 0540 } 0541 0542 eMyMoney::Split::InvestmentTransactionType Remove::type() const 0543 { 0544 return eMyMoney::Split::InvestmentTransactionType::RemoveShares; 0545 } 0546 0547 void Remove::showWidgets() const 0548 { 0549 static const QStringList activityWidgets = { 0550 // clang-format off 0551 "sharesLabel", "sharesAmountEdit", 0552 // clang-format on 0553 }; 0554 0555 setupWidgets(activityWidgets); 0556 } 0557 0558 void Remove::adjustStockSplit(MyMoneySplit& stockSplit) 0559 { 0560 stockSplit.setValue(MyMoneyMoney()); 0561 stockSplit.setPrice(MyMoneyMoney()); 0562 } 0563 0564 MyMoneyMoney Invest::Remove::totalAmount(const MyMoneySplit & stockSplit, const SplitModel * feesModel, const SplitModel * interestModel) const 0565 { 0566 Q_UNUSED(stockSplit) 0567 Q_UNUSED(feesModel) 0568 Q_UNUSED(interestModel) 0569 0570 return {}; 0571 } 0572 0573 0574 MyMoneyMoney Remove::sharesFactor() const 0575 { 0576 return MyMoneyMoney::MINUS_ONE; 0577 } 0578 0579 Invest::Split::Split(InvestTransactionEditor* editor) 0580 : Activity(editor, QLatin1String("Split")) 0581 { 0582 } 0583 0584 Invest::Split::~Split() 0585 { 0586 } 0587 0588 eMyMoney::Split::InvestmentTransactionType Invest::Split::type() const 0589 { 0590 return eMyMoney::Split::InvestmentTransactionType::SplitShares; 0591 } 0592 0593 void Invest::Split::showWidgets() const 0594 { 0595 static const QStringList activityWidgets = { 0596 // clang-format off 0597 "sharesLabel", "sharesAmountEdit", 0598 // clang-format on 0599 }; 0600 0601 setupWidgets(activityWidgets); 0602 } 0603 0604 0605 0606 QString Invest::Split::sharesLabelText() const 0607 { 0608 return i18nc("@label:textbox", "Ratio 1/"); 0609 } 0610 0611 void Invest::Split::adjustStockSplit(MyMoneySplit & stockSplit) 0612 { 0613 stockSplit.setValue(MyMoneyMoney()); 0614 stockSplit.setPrice(MyMoneyMoney()); 0615 } 0616 0617 MyMoneyMoney Invest::Split::totalAmount(const MyMoneySplit & stockSplit, const SplitModel * feesModel, const SplitModel * interestModel) const 0618 { 0619 Q_UNUSED(stockSplit) 0620 Q_UNUSED(feesModel) 0621 Q_UNUSED(interestModel) 0622 0623 return {}; 0624 } 0625 0626 IntInc::IntInc(InvestTransactionEditor* editor) 0627 : Activity(editor, QLatin1String("IntIncome")) 0628 { 0629 } 0630 0631 IntInc::~IntInc() 0632 { 0633 } 0634 0635 eMyMoney::Split::InvestmentTransactionType IntInc::type() const 0636 { 0637 return eMyMoney::Split::InvestmentTransactionType::InterestIncome; 0638 } 0639 0640 void IntInc::showWidgets() const 0641 { 0642 static const QStringList activityWidgets = { 0643 // clang-format off 0644 "assetAccountLabel", "assetAccountCombo", 0645 "feesLabel", "feesCombo", "feesAmountLabel", "feesAmountEdit", 0646 "interestLabel", "interestCombo", "interestAmountLabel", "interestAmountEdit", 0647 "totalLabel", "totalAmountEdit", 0648 // clang-format on 0649 }; 0650 0651 setupWidgets(activityWidgets); 0652 }