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 }