File indexing completed on 2024-05-12 16:42:06

0001 /*
0002     SPDX-FileCopyrightText: 2004-2017 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-FileCopyrightText: 2004 Kevin Tambascio <ktambascio@users.sourceforge.net>
0004     SPDX-FileCopyrightText: 2004-2006 Ace Jones <acejones@users.sourceforge.net>
0005     SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "kequitypriceupdatedlg.h"
0010 
0011 // ----------------------------------------------------------------------------
0012 // QT Includes
0013 
0014 #include <QPushButton>
0015 #include <QTimer>
0016 #include <QList>
0017 #include <QPointer>
0018 
0019 // ----------------------------------------------------------------------------
0020 // KDE Includes
0021 
0022 #include <KMessageBox>
0023 #include <KTextEdit>
0024 #include <KConfig>
0025 #include <KConfigGroup>
0026 #include <KSharedConfig>
0027 #include <KStandardGuiItem>
0028 #include <KLocalizedString>
0029 #include <KColorScheme>
0030 
0031 // ----------------------------------------------------------------------------
0032 // Project Includes
0033 
0034 #include "ui_kequitypriceupdatedlg.h"
0035 
0036 #include "mymoneyfile.h"
0037 #include "mymoneyaccount.h"
0038 #include "mymoneysecurity.h"
0039 #include "mymoneyprice.h"
0040 #include "webpricequote.h"
0041 #include "kequitypriceupdateconfdlg.h"
0042 #include "kmymoneyutils.h"
0043 #include "mymoneyexception.h"
0044 #include "dialogenums.h"
0045 
0046 #define WEBID_COL       0
0047 #define NAME_COL        1
0048 #define PRICE_COL       2
0049 #define DATE_COL        3
0050 #define KMMID_COL       4
0051 #define SOURCE_COL      5
0052 
0053 class KEquityPriceUpdateDlgPrivate
0054 {
0055     Q_DISABLE_COPY(KEquityPriceUpdateDlgPrivate)
0056     Q_DECLARE_PUBLIC(KEquityPriceUpdateDlg)
0057 
0058 public:
0059     explicit KEquityPriceUpdateDlgPrivate(KEquityPriceUpdateDlg *qq) :
0060         q_ptr(qq),
0061         ui(new Ui::KEquityPriceUpdateDlg),
0062         m_fUpdateAll(false),
0063         m_updatingPricePolicy(eDialogs::UpdatePrice::All)
0064     {
0065     }
0066 
0067     ~KEquityPriceUpdateDlgPrivate()
0068     {
0069         delete ui;
0070     }
0071 
0072     void init(const QString& securityId)
0073     {
0074         Q_Q(KEquityPriceUpdateDlg);
0075         ui->setupUi(q);
0076         m_fUpdateAll = false;
0077         QStringList headerList;
0078         headerList << i18n("ID") << i18nc("Equity name", "Name")
0079                    << i18n("Price") << i18n("Date");
0080 
0081         ui->lvEquityList->header()->setSortIndicator(0, Qt::AscendingOrder);
0082         ui->lvEquityList->setColumnWidth(NAME_COL, 125);
0083 
0084         // This is a "get it up and running" hack.  Will replace this in the future.
0085         headerList << i18nc("Internal identifier", "Internal ID")
0086                    << i18nc("Online quote source", "Source");
0087         ui->lvEquityList->setColumnWidth(KMMID_COL, 0);
0088 
0089         ui->lvEquityList->setHeaderLabels(headerList);
0090 
0091         ui->lvEquityList->setSelectionMode(QAbstractItemView::MultiSelection);
0092         ui->lvEquityList->setAllColumnsShowFocus(true);
0093 
0094         ui->btnUpdateAll->setEnabled(false);
0095 
0096         auto file = MyMoneyFile::instance();
0097 
0098         //
0099         // Add each price pair that we know about
0100         //
0101 
0102         // send in securityId == "XXX YYY" to get a single-shot update for XXX to YYY.
0103         // for consistency reasons, this accepts the same delimiters as WebPriceQuote::launch()
0104         QRegExp splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)", Qt::CaseInsensitive);
0105         MyMoneySecurityPair currencyIds;
0106         if (splitrx.indexIn(securityId) != -1) {
0107             currencyIds = MyMoneySecurityPair(splitrx.cap(1), splitrx.cap(2));
0108         }
0109 
0110         MyMoneyPriceList prices = file->priceList();
0111         for (MyMoneyPriceList::ConstIterator it_price = prices.constBegin(); it_price != prices.constEnd(); ++it_price) {
0112             const MyMoneySecurityPair& pair = it_price.key();
0113             if (file->security(pair.first).isCurrency() && (securityId.isEmpty() || (pair == currencyIds))) {
0114                 if (pair.first.trimmed().isEmpty() || pair.second.trimmed().isEmpty()) {
0115                     qDebug() << "A currency pair" << pair << "has one of its elements present, while the other one is empty. Omitting.";
0116                     continue;
0117                 }
0118                 if (!file->security(pair.second).isCurrency()) {
0119                     qDebug() << "A currency pair" << pair << "is invalid (from currency to equity). Omitting.";
0120                     continue;
0121                 }
0122                 const MyMoneyPriceEntries& entries = (*it_price);
0123                 if (entries.count() > 0 && entries.begin().key() <= QDate::currentDate()) {
0124                     addPricePair(pair, false);
0125                     ui->btnUpdateAll->setEnabled(true);
0126                 }
0127             }
0128         }
0129 
0130         //
0131         // Add each investment
0132         //
0133 
0134         QList<MyMoneySecurity> securities = file->securityList();
0135         for (QList<MyMoneySecurity>::const_iterator it = securities.constBegin(); it != securities.constEnd(); ++it) {
0136             if (!(*it).isCurrency() //
0137                     && (securityId.isEmpty() || ((*it).id() == securityId)) //
0138                     && !(*it).value("kmm-online-source").isEmpty()
0139                ) {
0140                 addInvestment(*it);
0141                 ui->btnUpdateAll->setEnabled(true);
0142             }
0143         }
0144 
0145         // if list is empty and a price pair has been requested, add it
0146         if (ui->lvEquityList->invisibleRootItem()->childCount() == 0 && !securityId.isEmpty()) {
0147             addPricePair(currencyIds, true);
0148         }
0149 
0150         q->connect(ui->btnUpdateSelected, &QAbstractButton::clicked, q, &KEquityPriceUpdateDlg::slotUpdateSelectedClicked);
0151         q->connect(ui->btnUpdateAll, &QAbstractButton::clicked, q, &KEquityPriceUpdateDlg::slotUpdateAllClicked);
0152 
0153         q->connect(ui->m_fromDate, &KMyMoneyDateInput::dateChanged, q, &KEquityPriceUpdateDlg::slotDateChanged);
0154         q->connect(ui->m_toDate, &KMyMoneyDateInput::dateChanged, q, &KEquityPriceUpdateDlg::slotDateChanged);
0155 
0156         q->connect(&m_webQuote, &WebPriceQuote::csvquote,
0157                    q, &KEquityPriceUpdateDlg::slotReceivedCSVQuote);
0158         q->connect(&m_webQuote, &WebPriceQuote::quote,
0159                    q, &KEquityPriceUpdateDlg::slotReceivedQuote);
0160         q->connect(&m_webQuote, &WebPriceQuote::failed,
0161                    q, &KEquityPriceUpdateDlg::slotQuoteFailed);
0162         q->connect(&m_webQuote, &WebPriceQuote::status,
0163                    q, &KEquityPriceUpdateDlg::logStatusMessage);
0164         q->connect(&m_webQuote, &WebPriceQuote::error,
0165                    q, &KEquityPriceUpdateDlg::logErrorMessage);
0166 
0167         q->connect(ui->lvEquityList, &QTreeWidget::itemSelectionChanged, q, &KEquityPriceUpdateDlg::slotUpdateSelection);
0168 
0169         q->connect(ui->btnConfigure, &QAbstractButton::clicked, q, &KEquityPriceUpdateDlg::slotConfigureClicked);
0170 
0171         if (!securityId.isEmpty()) {
0172             ui->btnUpdateSelected->hide();
0173             ui->btnUpdateAll->hide();
0174             // delete layout1;
0175 
0176             QTimer::singleShot(100, q, SLOT(slotUpdateAllClicked()));
0177         }
0178 
0179         // Hide OK button until we have received the first update
0180         ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
0181         if (ui->lvEquityList->invisibleRootItem()->childCount() == 0) {
0182             ui->btnUpdateAll->setEnabled(false);
0183         }
0184         q->slotUpdateSelection();
0185 
0186         // previous versions of this dialog allowed to store a "Don't ask again" switch.
0187         // Since we don't support it anymore, we just get rid of it
0188         KSharedConfigPtr config = KSharedConfig::openConfig();
0189         KConfigGroup grp = config->group("Notification Messages");
0190         grp.deleteEntry("KEquityPriceUpdateDlg::slotQuoteFailed::Price Update Failed");
0191         grp.sync();
0192         grp = config->group("Equity Price Update");
0193         int policyValue = grp.readEntry("PriceUpdatingPolicy", (int)eDialogs::UpdatePrice::Missing);
0194         if (policyValue > (int)eDialogs::UpdatePrice::Ask || policyValue < (int)eDialogs::UpdatePrice::All)
0195             m_updatingPricePolicy = eDialogs::UpdatePrice::Missing;
0196         else
0197             m_updatingPricePolicy = static_cast<eDialogs::UpdatePrice>(policyValue);
0198     }
0199 
0200     void addPricePair(const MyMoneySecurityPair& pair, bool dontCheckExistance)
0201     {
0202         Q_ASSERT(!pair.first.trimmed().isEmpty());
0203         Q_ASSERT(!pair.second.trimmed().isEmpty());
0204 
0205         auto file = MyMoneyFile::instance();
0206 
0207         const auto symbol = QString::fromLatin1("%1 > %2").arg(pair.first, pair.second);
0208         const auto id = QString::fromLatin1("%1 %2").arg(pair.first, pair.second);
0209         // Check that the pair does not already exist
0210         if (ui->lvEquityList->findItems(id, Qt::MatchExactly, KMMID_COL).empty()) {
0211             const MyMoneyPrice &pr = file->price(pair.first, pair.second);
0212             if (pr.source() != QLatin1String("KMyMoney")) {
0213                 bool keep = true;
0214                 if ((pair.first == file->baseCurrency().id())
0215                         || (pair.second == file->baseCurrency().id())) {
0216                     const QString& foreignCurrency = file->foreignCurrency(pair.first, pair.second);
0217                     // check that the foreign currency is still in use
0218                     QList<MyMoneyAccount>::const_iterator it_a;
0219                     QList<MyMoneyAccount> list;
0220                     file->accountList(list);
0221                     for (it_a = list.constBegin(); !dontCheckExistance && it_a != list.constEnd(); ++it_a) {
0222                         // if it's an account denominated in the foreign currency
0223                         // keep it
0224                         if (((*it_a).currencyId() == foreignCurrency)
0225                                 && !(*it_a).isClosed())
0226                             break;
0227                         // if it's an investment traded in the foreign currency
0228                         // keep it
0229                         if ((*it_a).isInvest() && !(*it_a).isClosed()) {
0230                             MyMoneySecurity sec = file->security((*it_a).currencyId());
0231                             if (sec.tradingCurrency() == foreignCurrency)
0232                                 break;
0233                         }
0234                     }
0235                     // if it is in use, it_a is not equal to list.end()
0236                     if (it_a == list.constEnd() && !dontCheckExistance)
0237                         keep = false;
0238                 }
0239 
0240                 if (keep) {
0241                     auto item = new QTreeWidgetItem();
0242                     item->setText(WEBID_COL, symbol);
0243                     item->setText(NAME_COL, i18n("%1 units in %2", pair.first, pair.second));
0244                     if (pr.isValid()) {
0245                         MyMoneySecurity fromCurrency = file->currency(pair.second);
0246                         MyMoneySecurity toCurrency = file->currency(pair.first);
0247                         item->setText(PRICE_COL, pr.rate(pair.second).formatMoney(fromCurrency.tradingSymbol(), toCurrency.pricePrecision()));
0248                         item->setText(DATE_COL, pr.date().toString(Qt::ISODate));
0249                     }
0250                     item->setText(KMMID_COL, id);
0251                     item->setText(SOURCE_COL, "KMyMoney Currency");  // This string value should not be localized
0252                     ui->lvEquityList->invisibleRootItem()->addChild(item);
0253                 }
0254             }
0255         }
0256     }
0257 
0258     void addInvestment(const MyMoneySecurity& inv)
0259     {
0260         const auto id = inv.id();
0261         // Check that the pair does not already exist
0262         if (ui->lvEquityList->findItems(id, Qt::MatchExactly, KMMID_COL).empty()) {
0263             auto file = MyMoneyFile::instance();
0264             // check that the security is still in use
0265             QList<MyMoneyAccount>::const_iterator it_a;
0266             QList<MyMoneyAccount> list;
0267             file->accountList(list);
0268             for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) {
0269                 if ((*it_a).isInvest()
0270                         && ((*it_a).currencyId() == inv.id())
0271                         && !(*it_a).isClosed())
0272                     break;
0273             }
0274             // if it is in use, it_a is not equal to list.end()
0275             if (it_a != list.constEnd()) {
0276                 QString webID;
0277                 WebPriceQuoteSource onlineSource(inv.value("kmm-online-source"));
0278                 if (onlineSource.m_webIDBy == WebPriceQuoteSource::identifyBy::IdentificationNumber)
0279                     webID = inv.value("kmm-security-id");   // insert ISIN number...
0280                 else if (onlineSource.m_webIDBy == WebPriceQuoteSource::identifyBy::Name)
0281                     webID = inv.name();                     // ...or name...
0282                 else
0283                     webID = inv.tradingSymbol();            // ...or symbol
0284 
0285                 QTreeWidgetItem* item = new QTreeWidgetItem();
0286                 item->setForeground(WEBID_COL, KColorScheme(QPalette::Normal).foreground(KColorScheme::NormalText));
0287                 if (webID.isEmpty()) {
0288                     webID = i18n("[No identifier]");
0289                     item->setForeground(WEBID_COL, KColorScheme(QPalette::Normal).foreground(KColorScheme::NegativeText));
0290                 }
0291                 item->setText(WEBID_COL, webID);
0292                 item->setText(NAME_COL, inv.name());
0293                 MyMoneySecurity currency = file->currency(inv.tradingCurrency());
0294                 const MyMoneyPrice &pr = file->price(id.toUtf8(), inv.tradingCurrency());
0295                 if (pr.isValid()) {
0296                     item->setText(PRICE_COL, pr.rate(currency.id()).formatMoney(currency.tradingSymbol(), inv.pricePrecision()));
0297                     item->setText(DATE_COL, pr.date().toString(Qt::ISODate));
0298                 }
0299                 item->setText(KMMID_COL, id);
0300                 if (inv.value("kmm-online-quote-system") == "Finance::Quote")
0301                     item->setText(SOURCE_COL, QString("Finance::Quote %1").arg(inv.value("kmm-online-source")));
0302                 else
0303                     item->setText(SOURCE_COL, inv.value("kmm-online-source"));
0304 
0305                 ui->lvEquityList->invisibleRootItem()->addChild(item);
0306 
0307                 // If this investment is denominated in a foreign currency, ensure that
0308                 // the appropriate price pair is also on the list
0309 
0310                 if (currency.id() != file->baseCurrency().id()) {
0311                     addPricePair(MyMoneySecurityPair(currency.id(), file->baseCurrency().id()), false);
0312                 }
0313             }
0314         }
0315     }
0316 
0317     KEquityPriceUpdateDlg      *q_ptr;
0318     Ui::KEquityPriceUpdateDlg  *ui;
0319     bool                        m_fUpdateAll;
0320     eDialogs::UpdatePrice        m_updatingPricePolicy;
0321     WebPriceQuote               m_webQuote;
0322 };
0323 
0324 KEquityPriceUpdateDlg::KEquityPriceUpdateDlg(QWidget *parent, const QString& securityId) :
0325     QDialog(parent),
0326     d_ptr(new KEquityPriceUpdateDlgPrivate(this))
0327 {
0328     Q_D(KEquityPriceUpdateDlg);
0329     d->init(securityId);
0330 }
0331 
0332 KEquityPriceUpdateDlg::~KEquityPriceUpdateDlg()
0333 {
0334     Q_D(KEquityPriceUpdateDlg);
0335     auto config = KSharedConfig::openConfig();
0336     auto grp = config->group("Equity Price Update");
0337     grp.writeEntry("PriceUpdatingPolicy", static_cast<int>(d->m_updatingPricePolicy));
0338     grp.sync();
0339     delete d;
0340 }
0341 
0342 void KEquityPriceUpdateDlg::logErrorMessage(const QString& message)
0343 {
0344     logStatusMessage(QString("<font color=\"red\"><b>") + message + QString("</b></font>"));
0345 }
0346 
0347 void KEquityPriceUpdateDlg::logStatusMessage(const QString& message)
0348 {
0349     Q_D(KEquityPriceUpdateDlg);
0350     d->ui->lbStatus->append(message);
0351 }
0352 
0353 MyMoneyPrice KEquityPriceUpdateDlg::price(const QString& id) const
0354 {
0355     Q_D(const KEquityPriceUpdateDlg);
0356     MyMoneyPrice price;
0357     QTreeWidgetItem* item = nullptr;
0358     QList<QTreeWidgetItem*> foundItems = d->ui->lvEquityList->findItems(id, Qt::MatchExactly, KMMID_COL);
0359 
0360     if (! foundItems.empty())
0361         item = foundItems.at(0);
0362 
0363     if (item) {
0364         MyMoneyMoney rate(item->text(PRICE_COL));
0365         if (!rate.isZero()) {
0366             QString kmm_id = item->text(KMMID_COL).toUtf8();
0367 
0368             // if the ID has a space, then this is TWO ID's, so it's a currency quote
0369             if (kmm_id.contains(" ")) {
0370                 QStringList ids = kmm_id.split(' ', QString::SkipEmptyParts);
0371                 QString fromid = ids[0].toUtf8();
0372                 QString toid = ids[1].toUtf8();
0373                 price = MyMoneyPrice(fromid, toid, QDate().fromString(item->text(DATE_COL), Qt::ISODate), rate, item->text(SOURCE_COL));
0374             } else
0375                 // otherwise, it's a security quote
0376             {
0377                 MyMoneySecurity security = MyMoneyFile::instance()->security(kmm_id);
0378                 price = MyMoneyPrice(kmm_id, security.tradingCurrency(), QDate().fromString(item->text(DATE_COL), Qt::ISODate), rate, item->text(SOURCE_COL));
0379             }
0380         }
0381     }
0382     return price;
0383 }
0384 
0385 void KEquityPriceUpdateDlg::storePrices()
0386 {
0387     Q_D(KEquityPriceUpdateDlg);
0388     // update the new prices into the equities
0389 
0390     auto file = MyMoneyFile::instance();
0391     QString name;
0392 
0393     MyMoneyFileTransaction ft;
0394     try {
0395         for (auto i = 0; i < d->ui->lvEquityList->invisibleRootItem()->childCount(); ++i) {
0396             QTreeWidgetItem* item = d->ui->lvEquityList->invisibleRootItem()->child(i);
0397             // turn on signals before we modify the last entry in the list
0398             file->blockSignals(i < d->ui->lvEquityList->invisibleRootItem()->childCount() - 1);
0399 
0400             MyMoneyMoney rate(item->text(PRICE_COL));
0401             if (!rate.isZero()) {
0402                 QString id = item->text(KMMID_COL);
0403                 QString fromid;
0404                 QString toid;
0405 
0406                 // if the ID has a space, then this is TWO ID's, so it's a currency quote
0407                 if (id.contains(QLatin1Char(' '))) {
0408                     QStringList ids = id.split(QLatin1Char(' '), QString::SkipEmptyParts);
0409                     fromid = ids.at(0);
0410                     toid = ids.at(1);
0411                     name = QString::fromLatin1("%1 --> %2").arg(fromid, toid);
0412                 } else { // otherwise, it's a security quote
0413                     MyMoneySecurity security = file->security(id);
0414                     name = security.name();
0415                     fromid = id;
0416                     toid = security.tradingCurrency();
0417                 }
0418                 // TODO (Ace) Better handling of the case where there is already a price
0419                 // for this date.  Currently, it just overrides the old value.  Really it
0420                 // should check to see if the price is the same and prompt the user.
0421                 file->addPrice(MyMoneyPrice(fromid, toid, QDate::fromString(item->text(DATE_COL), Qt::ISODate), rate, item->text(SOURCE_COL)));
0422             }
0423         }
0424         ft.commit();
0425 
0426     } catch (const MyMoneyException &) {
0427         qDebug("Unable to add price information for %s", qPrintable(name));
0428     }
0429 }
0430 
0431 void KEquityPriceUpdateDlg::slotConfigureClicked()
0432 {
0433     Q_D(KEquityPriceUpdateDlg);
0434     QPointer<EquityPriceUpdateConfDlg> dlg = new EquityPriceUpdateConfDlg(d->m_updatingPricePolicy);
0435     if (dlg->exec() == QDialog::Accepted)
0436         d->m_updatingPricePolicy = dlg->policy();
0437     delete dlg;
0438 }
0439 
0440 void KEquityPriceUpdateDlg::slotUpdateSelection()
0441 {
0442     Q_D(KEquityPriceUpdateDlg);
0443     // Only enable the update button if there is a selection
0444     d->ui->btnUpdateSelected->setEnabled(false);
0445 
0446     if (! d->ui->lvEquityList->selectedItems().empty())
0447         d->ui->btnUpdateSelected->setEnabled(true);
0448 }
0449 
0450 void KEquityPriceUpdateDlg::slotUpdateSelectedClicked()
0451 {
0452     Q_D(KEquityPriceUpdateDlg);
0453     // disable sorting while the update is running to maintain the current order of items on which
0454     // the update process depends and which could be changed with sorting enabled due to the updated values
0455     d->ui->lvEquityList->setSortingEnabled(false);
0456     auto item = d->ui->lvEquityList->invisibleRootItem()->child(0);
0457     auto skipCnt = 1;
0458     while (item && !item->isSelected()) {
0459         item = d->ui->lvEquityList->invisibleRootItem()->child(skipCnt);
0460         ++skipCnt;
0461     }
0462     d->m_webQuote.setDate(d->ui->m_fromDate->date(), d->ui->m_toDate->date());
0463     if (item) {
0464         d->ui->prgOnlineProgress->setMaximum(1 + d->ui->lvEquityList->invisibleRootItem()->childCount());
0465         d->ui->prgOnlineProgress->setValue(skipCnt);
0466         d->m_webQuote.launch(item->text(WEBID_COL), item->text(KMMID_COL), item->text(SOURCE_COL));
0467 
0468     } else {
0469 
0470         logErrorMessage("No security selected.");
0471     }
0472 }
0473 
0474 void KEquityPriceUpdateDlg::slotUpdateAllClicked()
0475 {
0476     Q_D(KEquityPriceUpdateDlg);
0477     // disable sorting while the update is running to maintain the current order of items on which
0478     // the update process depends and which could be changed with sorting enabled due to the updated values
0479     d->ui->lvEquityList->setSortingEnabled(false);
0480     QTreeWidgetItem* item = d->ui->lvEquityList->invisibleRootItem()->child(0);
0481     if (item) {
0482         d->ui->prgOnlineProgress->setMaximum(1 + d->ui->lvEquityList->invisibleRootItem()->childCount());
0483         d->ui->prgOnlineProgress->setValue(1);
0484         d->m_fUpdateAll = true;
0485         d->m_webQuote.launch(item->text(WEBID_COL), item->text(KMMID_COL), item->text(SOURCE_COL));
0486 
0487     } else {
0488         logErrorMessage("Security list is empty.");
0489     }
0490 }
0491 
0492 void KEquityPriceUpdateDlg::slotDateChanged()
0493 {
0494     Q_D(KEquityPriceUpdateDlg);
0495     d->ui->m_fromDate->blockSignals(true);
0496     d->ui->m_toDate->blockSignals(true);
0497     if (d->ui->m_toDate->date() > QDate::currentDate())
0498         d->ui->m_toDate->setDate(QDate::currentDate());
0499     if (d->ui->m_fromDate->date() > d->ui->m_toDate->date())
0500         d->ui->m_fromDate->setDate(d->ui->m_toDate->date());
0501     d->ui->m_fromDate->blockSignals(false);
0502     d->ui->m_toDate->blockSignals(false);
0503 }
0504 
0505 void KEquityPriceUpdateDlg::slotQuoteFailed(const QString& _kmmID, const QString& _webID)
0506 {
0507     Q_D(KEquityPriceUpdateDlg);
0508     auto foundItems = d->ui->lvEquityList->findItems(_kmmID, Qt::MatchExactly, KMMID_COL);
0509     QTreeWidgetItem* item = nullptr;
0510 
0511     if (! foundItems.empty())
0512         item = foundItems.at(0);
0513 
0514     // Give the user some options
0515     int result;
0516     if (_kmmID.contains(" ")) {
0517         if (item)
0518             result = KMessageBox::warningContinueCancel(this, i18n("Failed to retrieve an exchange rate for %1 from %2. It will be skipped this time.", _webID, item->text(SOURCE_COL)), i18n("Price Update Failed"));
0519         else
0520             return;
0521     } else if (!item) {
0522         return;
0523     } else {
0524         result = KMessageBox::questionYesNoCancel(this, QString::fromLatin1("<qt>%1</qt>").arg(i18n("Failed to retrieve a quote for %1 from %2.  Press <b>No</b> to remove the online price source from this security permanently, <b>Yes</b> to continue updating this security during future price updates or <b>Cancel</b> to stop the current update operation.", _webID, item->text(SOURCE_COL))), i18n("Price Update Failed"), KStandardGuiItem::yes(), KStandardGuiItem::no());
0525     }
0526 
0527 
0528     if (result == KMessageBox::No) {
0529         // Disable price updates for this security
0530 
0531         MyMoneyFileTransaction ft;
0532         try {
0533             // Get this security (by ID)
0534             MyMoneySecurity security = MyMoneyFile::instance()->security(_kmmID.toUtf8());
0535 
0536             // Set the quote source to blank
0537             security.setValue("kmm-online-source", QString());
0538             security.setValue("kmm-online-quote-system", QString());
0539 
0540             // Re-commit the security
0541             MyMoneyFile::instance()->modifySecurity(security);
0542             ft.commit();
0543         } catch (const MyMoneyException &e) {
0544             KMessageBox::error(this, QString("<qt>") + i18n("Cannot update security <b>%1</b>: %2", _webID, QString::fromLatin1(e.what())) + QString("</qt>"), i18n("Price Update Failed"));
0545         }
0546     }
0547 
0548     // As long as the user doesn't want to cancel, move on!
0549     if (result != KMessageBox::Cancel) {
0550         QTreeWidgetItem* next = nullptr;
0551         d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1);
0552         item->setSelected(false);
0553 
0554         // launch the NEXT one ... in case of m_fUpdateAll == false, we
0555         // need to parse the list to find the next selected one
0556         next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(item) + 1);
0557         if (!d->m_fUpdateAll) {
0558             while (next && !next->isSelected()) {
0559                 d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1);
0560                 next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(next) + 1);
0561             }
0562         }
0563         if (next) {
0564             d->m_webQuote.launch(next->text(WEBID_COL), next->text(KMMID_COL), next->text(SOURCE_COL));
0565         } else {
0566             finishUpdate();
0567         }
0568     } else {
0569         finishUpdate();
0570     }
0571 }
0572 
0573 void KEquityPriceUpdateDlg::slotReceivedCSVQuote(const QString& _kmmID, const QString& _webID, MyMoneyStatement& st)
0574 {
0575     Q_D(KEquityPriceUpdateDlg);
0576     auto foundItems = d->ui->lvEquityList->findItems(_kmmID, Qt::MatchExactly, KMMID_COL);
0577     QTreeWidgetItem* item = nullptr;
0578 
0579     if (! foundItems.empty())
0580         item = foundItems.at(0);
0581 
0582     QTreeWidgetItem* next = nullptr;
0583 
0584     if (item) {
0585         auto file = MyMoneyFile::instance();
0586         MyMoneySecurity fromCurrency, toCurrency;
0587 
0588         if (!_kmmID.contains(QLatin1Char(' '))) {
0589             try {
0590                 toCurrency = MyMoneyFile::instance()->security(_kmmID);
0591                 fromCurrency = MyMoneyFile::instance()->security(toCurrency.tradingCurrency());
0592             } catch (const MyMoneyException &) {
0593                 fromCurrency = toCurrency = MyMoneySecurity();
0594             }
0595 
0596         } else {
0597             QRegExp splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)", Qt::CaseInsensitive);
0598             if (splitrx.indexIn(_kmmID) != -1) {
0599                 try {
0600                     fromCurrency = MyMoneyFile::instance()->security(splitrx.cap(2).toUtf8());
0601                     toCurrency = MyMoneyFile::instance()->security(splitrx.cap(1).toUtf8());
0602                 } catch (const MyMoneyException &) {
0603                     fromCurrency = toCurrency = MyMoneySecurity();
0604                 }
0605             }
0606         }
0607 
0608         if (d->m_updatingPricePolicy != eDialogs::UpdatePrice::All) {
0609             QStringList qSources = WebPriceQuote::quoteSources();
0610             for (auto it = st.m_listPrices.begin(); it != st.m_listPrices.end();) {
0611                 MyMoneyPrice storedPrice = file->price(toCurrency.id(), fromCurrency.id(), (*it).m_date, true);
0612                 bool priceValid = storedPrice.isValid();
0613                 if (!priceValid)
0614                     ++it;
0615                 else {
0616                     switch(d->m_updatingPricePolicy) {
0617                     case eDialogs::UpdatePrice::Missing:
0618                         it = st.m_listPrices.erase(it);
0619                         break;
0620                     case eDialogs::UpdatePrice::Downloaded:
0621                         if (!qSources.contains(storedPrice.source()))
0622                             it = st.m_listPrices.erase(it);
0623                         else
0624                             ++it;
0625                         break;
0626                     case eDialogs::UpdatePrice::SameSource:
0627                         if (storedPrice.source().compare((*it).m_sourceName) != 0)
0628                             it = st.m_listPrices.erase(it);
0629                         else
0630                             ++it;
0631                         break;
0632                     case eDialogs::UpdatePrice::Ask:
0633                     {
0634                         auto result = KMessageBox::questionYesNoCancel(this,
0635                                       i18n("For <b>%1</b> on <b>%2</b> price <b>%3</b> already exists.<br>"
0636                                            "Do you want to replace it with <b>%4</b>?",
0637                                            storedPrice.from(), storedPrice.date().toString(Qt::ISODate),
0638                                            QString().setNum(storedPrice.rate(storedPrice.to()).toDouble(), 'g', 10),
0639                                            QString().setNum((*it).m_amount.toDouble(), 'g', 10)),
0640                                       i18n("Price Already Exists"));
0641                         switch(result) {
0642                         case KMessageBox::ButtonCode::Yes:
0643                             ++it;
0644                             break;
0645                         case KMessageBox::ButtonCode::No:
0646                             it = st.m_listPrices.erase(it);
0647                             break;
0648                         default:
0649                         case KMessageBox::ButtonCode::Cancel:
0650                             finishUpdate();
0651                             return;
0652                             break;
0653                         }
0654                         break;
0655                     }
0656                     default:
0657                         ++it;
0658                         break;
0659                     }
0660                 }
0661             }
0662         }
0663 
0664         if (!st.m_listPrices.isEmpty()) {
0665             MyMoneyFileTransaction ft;
0666             KMyMoneyUtils::processPriceList(st);
0667             ft.commit();
0668 
0669             // latest price could be in the last or in the first row
0670             MyMoneyStatement::Price priceClass;
0671             if (st.m_listPrices.first().m_date > st.m_listPrices.last().m_date)
0672                 priceClass = st.m_listPrices.first();
0673             else
0674                 priceClass = st.m_listPrices.last();
0675 
0676             // update latest price in dialog if applicable
0677             auto latestDate = QDate::fromString(item->text(DATE_COL),Qt::ISODate);
0678             if (latestDate <= priceClass.m_date && priceClass.m_amount.isPositive()) {
0679                 item->setText(PRICE_COL, priceClass.m_amount.formatMoney(fromCurrency.tradingSymbol(), toCurrency.pricePrecision()));
0680                 item->setText(DATE_COL, priceClass.m_date.toString(Qt::ISODate));
0681                 item->setText(SOURCE_COL, priceClass.m_sourceName);
0682             }
0683             logStatusMessage(i18n("Price for %1 updated (id %2)", _webID, _kmmID));
0684             // make sure to make OK button available
0685         }
0686         d->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
0687 
0688         d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1);
0689         item->setSelected(false);
0690 
0691         // launch the NEXT one ... in case of m_fUpdateAll == false, we
0692         // need to parse the list to find the next selected one
0693         next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(item) + 1);
0694         if (!d->m_fUpdateAll) {
0695             while (next && !next->isSelected()) {
0696                 d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1);
0697                 next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(next) + 1);
0698             }
0699         }
0700     } else {
0701         logErrorMessage(i18n("Received a price for %1 (id %2), but this symbol is not on the list. Aborting entire update.", _webID, _kmmID));
0702     }
0703 
0704     if (next) {
0705         d->m_webQuote.launch(next->text(WEBID_COL), next->text(KMMID_COL), next->text(SOURCE_COL));
0706     } else {
0707         finishUpdate();
0708     }
0709 }
0710 
0711 void KEquityPriceUpdateDlg::slotReceivedQuote(const QString& _kmmID, const QString& _webID, const QDate& _date, const double& _price)
0712 {
0713     Q_D(KEquityPriceUpdateDlg);
0714     auto foundItems = d->ui->lvEquityList->findItems(_kmmID, Qt::MatchExactly, KMMID_COL);
0715     QTreeWidgetItem* item = nullptr;
0716 
0717     if (! foundItems.empty())
0718         item = foundItems.at(0);
0719 
0720     QTreeWidgetItem* next = 0;
0721 
0722     if (item) {
0723         if (_price > 0.0f && _date.isValid()) {
0724             QDate date = _date;
0725             if (date > QDate::currentDate())
0726                 date = QDate::currentDate();
0727 
0728             MyMoneyMoney price = MyMoneyMoney::ONE;
0729             QString id = _kmmID.toUtf8();
0730             MyMoneySecurity fromCurrency, toCurrency;
0731             if (_kmmID.contains(" ") == 0) {
0732                 MyMoneySecurity security = MyMoneyFile::instance()->security(id);
0733                 QString factor = security.value("kmm-online-factor");
0734                 if (!factor.isEmpty()) {
0735                     price = price * MyMoneyMoney(factor);
0736                 }
0737                 try {
0738                     toCurrency = MyMoneyFile::instance()->security(id);
0739                     fromCurrency = MyMoneyFile::instance()->security(toCurrency.tradingCurrency());
0740                 } catch (const MyMoneyException &) {
0741                     fromCurrency = toCurrency = MyMoneySecurity();
0742                 }
0743 
0744             } else {
0745                 QRegExp splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)", Qt::CaseInsensitive);
0746                 if (splitrx.indexIn(_kmmID) != -1) {
0747                     try {
0748                         fromCurrency = MyMoneyFile::instance()->security(splitrx.cap(2).toUtf8());
0749                         toCurrency = MyMoneyFile::instance()->security(splitrx.cap(1).toUtf8());
0750                     } catch (const MyMoneyException &) {
0751                         fromCurrency = toCurrency = MyMoneySecurity();
0752                     }
0753                 }
0754             }
0755             price *= MyMoneyMoney(_price, MyMoneyMoney::precToDenom(toCurrency.pricePrecision()));
0756 
0757             item->setText(PRICE_COL, price.formatMoney(fromCurrency.tradingSymbol(), toCurrency.pricePrecision()));
0758             item->setText(DATE_COL, date.toString(Qt::ISODate));
0759             logStatusMessage(i18n("Price for %1 updated (id %2)", _webID, _kmmID));
0760             // make sure to make OK button available
0761             d->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
0762         } else {
0763             logErrorMessage(i18n("Received an invalid price for %1, unable to update.", _webID));
0764         }
0765 
0766         d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1);
0767         item->setSelected(false);
0768 
0769         // launch the NEXT one ... in case of m_fUpdateAll == false, we
0770         // need to parse the list to find the next selected one
0771         next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(item) + 1);
0772         if (!d->m_fUpdateAll) {
0773             while (next && !next->isSelected()) {
0774                 d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1);
0775                 next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(next) + 1);
0776             }
0777         }
0778     } else {
0779         logErrorMessage(i18n("Received a price for %1 (id %2), but this symbol is not on the list. Aborting entire update.", _webID, _kmmID));
0780     }
0781 
0782     if (next) {
0783         d->m_webQuote.launch(next->text(WEBID_COL), next->text(KMMID_COL), next->text(SOURCE_COL));
0784     } else {
0785         finishUpdate();
0786     }
0787 }
0788 
0789 void KEquityPriceUpdateDlg::finishUpdate()
0790 {
0791     Q_D(KEquityPriceUpdateDlg);
0792     // we've run past the end, reset to the default value.
0793     d->m_fUpdateAll = false;
0794     // force progress bar to show 100%
0795     d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->maximum());
0796     // re-enable the sorting that was disabled during the update process
0797     d->ui->lvEquityList->setSortingEnabled(true);
0798 }
0799 
0800 // Make sure, that these definitions are only used within this file
0801 // this does not seem to be necessary, but when building RPMs the
0802 // build option 'final' is used and all CPP files are concatenated.
0803 // So it could well be, that in another CPP file these definitions
0804 // are also used.
0805 #undef WEBID_COL
0806 #undef NAME_COL
0807 #undef PRICE_COL
0808 #undef DATE_COL
0809 #undef KMMID_COL
0810 #undef SOURCE_COL