File indexing completed on 2024-04-28 16:29:36

0001 /*
0002     SPDX-FileCopyrightText: 2000-2003 Michael Edwardes <mte@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2000-2003 Javier Campos Morales <javi_c@users.sourceforge.net>
0004     SPDX-FileCopyrightText: 2000-2003 Felix Rodriguez <frodriguez@users.sourceforge.net>
0005     SPDX-FileCopyrightText: 2000-2003 John C <thetacoturtle@users.sourceforge.net>
0006     SPDX-FileCopyrightText: 2000-2003 Thomas Baumgart <ipwizard@users.sourceforge.net>
0007     SPDX-FileCopyrightText: 2000-2003 Kevin Tambascio <ktambascio@users.sourceforge.net>
0008     SPDX-FileCopyrightText: 2017 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include "kmymoneyutils.h"
0013 
0014 // ----------------------------------------------------------------------------
0015 // QT Includes
0016 
0017 #include <QWidget>
0018 #include <QApplication>
0019 #include <QList>
0020 #include <QPixmap>
0021 #include <QWizard>
0022 #include <QAbstractButton>
0023 #include <QPixmapCache>
0024 #include <QIcon>
0025 #include <QPainter>
0026 #include <QBitArray>
0027 #include <QRegularExpression>
0028 #include <QRegularExpressionMatch>
0029 #include <QTemporaryFile>
0030 #include <QFileInfo>
0031 
0032 // ----------------------------------------------------------------------------
0033 // KDE Headers
0034 
0035 #include <KColorScheme>
0036 #include <KGuiItem>
0037 #include <KIO/StatJob>
0038 #include <KIO/StoredTransferJob>
0039 #include <KLocalizedString>
0040 #include <KMessageBox>
0041 #include <KStandardGuiItem>
0042 #include <KXmlGuiWindow>
0043 #include <kio_version.h>
0044 
0045 // ----------------------------------------------------------------------------
0046 // Project Includes
0047 
0048 #include "mymoneymoney.h"
0049 #include "mymoneyexception.h"
0050 #include "mymoneytransactionfilter.h"
0051 #include "mymoneyfile.h"
0052 #include "mymoneyaccount.h"
0053 #include "mymoneysecurity.h"
0054 #include "mymoneyschedule.h"
0055 #include "mymoneypayee.h"
0056 #include "mymoneytag.h"
0057 #include "mymoneyprice.h"
0058 #include "mymoneystatement.h"
0059 #include "mymoneyforecast.h"
0060 #include "mymoneysplit.h"
0061 #include "mymoneytransaction.h"
0062 #include "kmymoneysettings.h"
0063 #include "icons.h"
0064 #include "storageenums.h"
0065 #include "mymoneyenums.h"
0066 #include "kmymoneyplugin.h"
0067 
0068 using namespace Icons;
0069 
0070 KMyMoneyUtils::KMyMoneyUtils()
0071 {
0072 }
0073 
0074 KMyMoneyUtils::~KMyMoneyUtils()
0075 {
0076 }
0077 
0078 const QString KMyMoneyUtils::occurrenceToString(const eMyMoney::Schedule::Occurrence occurrence)
0079 {
0080     return i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(occurrence).toLatin1());
0081 }
0082 
0083 const QString KMyMoneyUtils::paymentMethodToString(eMyMoney::Schedule::PaymentType paymentType)
0084 {
0085     return i18nc("Scheduled Transaction payment type", MyMoneySchedule::paymentMethodToString(paymentType).toLatin1());
0086 }
0087 
0088 const QString KMyMoneyUtils::weekendOptionToString(eMyMoney::Schedule::WeekendOption weekendOption)
0089 {
0090     return i18n(MyMoneySchedule::weekendOptionToString(weekendOption).toLatin1());
0091 }
0092 
0093 const QString KMyMoneyUtils::scheduleTypeToString(eMyMoney::Schedule::Type type)
0094 {
0095     return i18nc("Scheduled transaction type", MyMoneySchedule::scheduleTypeToString(type).toLatin1());
0096 }
0097 
0098 KGuiItem KMyMoneyUtils::scheduleNewGuiItem()
0099 {
0100     KGuiItem splitGuiItem(i18n("&New Schedule..."),
0101                           Icons::get(Icon::DocumentNew),
0102                           i18n("Create a new schedule."),
0103                           i18n("Use this to create a new schedule."));
0104 
0105     return splitGuiItem;
0106 }
0107 
0108 KGuiItem KMyMoneyUtils::accountsFilterGuiItem()
0109 {
0110     KGuiItem splitGuiItem(i18n("&Filter"),
0111                           Icons::get(Icon::Filter),
0112                           i18n("Filter out accounts"),
0113                           i18n("Use this to filter out accounts"));
0114 
0115     return splitGuiItem;
0116 }
0117 
0118 const char* homePageItems[] = {
0119     I18N_NOOP("Payments"),
0120     I18N_NOOP("Preferred accounts"),
0121     I18N_NOOP("Payment accounts"),
0122     I18N_NOOP("Favorite reports"),
0123     I18N_NOOP("Forecast (schedule)"),
0124     I18N_NOOP("Net worth forecast"),
0125     I18N_NOOP("Forecast (history)"),        // unused, s.a. KSettingsHome::slotLoadItems()
0126     I18N_NOOP("Assets and Liabilities"),
0127     I18N_NOOP("Budget"),
0128     I18N_NOOP("CashFlow"),
0129     // insert new items above this comment
0130     0,
0131 };
0132 
0133 const QString KMyMoneyUtils::homePageItemToString(const int idx)
0134 {
0135     QString rc;
0136     if (abs(idx) > 0 && abs(idx) < static_cast<int>(sizeof(homePageItems) / sizeof(homePageItems[0]))) {
0137         rc = i18n(homePageItems[abs(idx-1)]);
0138     }
0139     return rc;
0140 }
0141 
0142 int KMyMoneyUtils::stringToHomePageItem(const QString& txt)
0143 {
0144     int idx = 0;
0145     for (idx = 0; homePageItems[idx] != 0; ++idx) {
0146         if (txt == i18n(homePageItems[idx]))
0147             return idx + 1;
0148     }
0149     return 0;
0150 }
0151 
0152 bool KMyMoneyUtils::appendCorrectFileExt(QString& str, const QString& strExtToUse)
0153 {
0154     bool rc = false;
0155 
0156     if (!str.isEmpty()) {
0157         //find last . deliminator
0158         int nLoc = str.lastIndexOf('.');
0159         if (nLoc != -1) {
0160             QString strExt, strTemp;
0161             strTemp = str.left(nLoc + 1);
0162             strExt = str.right(str.length() - (nLoc + 1));
0163             if (strExt.indexOf(strExtToUse, 0, Qt::CaseInsensitive) == -1) {
0164                 // if the extension given contains a period, we remove ours
0165                 if (strExtToUse.indexOf('.') != -1)
0166                     strTemp = strTemp.left(strTemp.length() - 1);
0167                 //append extension to make complete file name
0168                 strTemp.append(strExtToUse);
0169                 str = strTemp;
0170                 rc = true;
0171             }
0172         } else {
0173             str.append(QLatin1Char('.'));
0174             str.append(strExtToUse);
0175             rc = true;
0176         }
0177     }
0178     return rc;
0179 }
0180 
0181 void KMyMoneyUtils::checkConstants()
0182 {
0183     // TODO: port to kf5
0184 #if 0
0185     Q_ASSERT(static_cast<int>(KLocale::ParensAround) == static_cast<int>(MyMoneyMoney::ParensAround));
0186     Q_ASSERT(static_cast<int>(KLocale::BeforeQuantityMoney) == static_cast<int>(MyMoneyMoney::BeforeQuantityMoney));
0187     Q_ASSERT(static_cast<int>(KLocale::AfterQuantityMoney) == static_cast<int>(MyMoneyMoney::AfterQuantityMoney));
0188     Q_ASSERT(static_cast<int>(KLocale::BeforeMoney) == static_cast<int>(MyMoneyMoney::BeforeMoney));
0189     Q_ASSERT(static_cast<int>(KLocale::AfterMoney) == static_cast<int>(MyMoneyMoney::AfterMoney));
0190 #endif
0191 }
0192 
0193 QString KMyMoneyUtils::variableCSS()
0194 {
0195     QColor tcolor = KColorScheme(QPalette::Active).foreground(KColorScheme::NormalText).color();
0196     QColor link = KColorScheme(QPalette::Active).foreground(KColorScheme::LinkText).color();
0197 
0198     QString css;
0199     css += "<style type=\"text/css\">\n<!--\n";
0200     css += QString(".row-even, .item0 { background-color: %1; color: %2 }\n")
0201            .arg(KMyMoneySettings::schemeColor(SchemeColor::ListBackground1).name()).arg(tcolor.name());
0202     css += QString(".row-odd, .item1  { background-color: %1; color: %2 }\n")
0203            .arg(KMyMoneySettings::schemeColor(SchemeColor::ListBackground2).name()).arg(tcolor.name());
0204     css += QString("a { color: %1 }\n").arg(link.name());
0205     css += "-->\n</style>\n";
0206     return css;
0207 }
0208 
0209 QString KMyMoneyUtils::findResource(QStandardPaths::StandardLocation type, const QString& filename)
0210 {
0211     QLocale locale;
0212     QString country;
0213     QString localeName = locale.bcp47Name();
0214     QString language = localeName;
0215 
0216     // extract language and country from the bcp47name
0217     QRegularExpression regExp(QLatin1String("(\\w+)_(\\w+)"));
0218     QRegularExpressionMatch match = regExp.match(localeName);
0219     if (match.hasMatch()) {
0220         language = match.captured(1);
0221         country = match.captured(2);
0222     }
0223 
0224     QString rc;
0225 
0226     // check that the placeholder is present and set things up
0227     if (filename.indexOf("%1") != -1) {
0228         /// @fixme somehow I have the impression, that language and country
0229         ///    mappings to the filename are not correct. This certainly must
0230         ///    be overhauled at some point in time (ipwizard, 2017-10-22)
0231         QString mask = filename.arg("_%1.%2");
0232         rc = QStandardPaths::locate(type, mask.arg(country, language));
0233 
0234         // search the given resource
0235         if (rc.isEmpty()) {
0236             mask = filename.arg("_%1");
0237             rc = QStandardPaths::locate(type, mask.arg(language));
0238         }
0239         if (rc.isEmpty()) {
0240             // qDebug(QString("html/home_%1.html not found").arg(country).toLatin1());
0241             rc = QStandardPaths::locate(type, mask.arg(country));
0242         }
0243         if (rc.isEmpty()) {
0244             rc = QStandardPaths::locate(type, filename.arg(""));
0245         }
0246     } else {
0247         rc = QStandardPaths::locate(type, filename);
0248     }
0249 
0250     if (rc.isEmpty()) {
0251         qWarning("No resource found for (%s,%s)", qPrintable(QStandardPaths::displayName(type)), qPrintable(filename));
0252     }
0253     return rc;
0254 }
0255 
0256 const MyMoneySplit KMyMoneyUtils::stockSplit(const MyMoneyTransaction& t)
0257 {
0258     MyMoneySplit investmentAccountSplit;
0259     foreach (const auto split, t.splits()) {
0260         if (!split.accountId().isEmpty()) {
0261             auto acc = MyMoneyFile::instance()->account(split.accountId());
0262             if (acc.isInvest()) {
0263                 return split;
0264             }
0265             // if we have a reference to an investment account, we remember it here
0266             if (acc.accountType() == eMyMoney::Account::Type::Investment)
0267                 investmentAccountSplit = split;
0268         }
0269     }
0270     // if we haven't found a stock split, we see if we've seen
0271     // an investment account on the way. If so, we return it.
0272     if (!investmentAccountSplit.id().isEmpty())
0273         return investmentAccountSplit;
0274 
0275     // if none was found, we return an empty split.
0276     return MyMoneySplit();
0277 }
0278 
0279 KMyMoneyUtils::transactionTypeE KMyMoneyUtils::transactionType(const MyMoneyTransaction& t)
0280 {
0281     if (!stockSplit(t).id().isEmpty())
0282         return InvestmentTransaction;
0283 
0284     if (t.splitCount() < 2) {
0285         return Unknown;
0286     } else if (t.splitCount() > 2) {
0287         // FIXME check for loan transaction here
0288         return SplitTransaction;
0289     }
0290     QString ida, idb;
0291     const auto & splits = t.splits();
0292     if (splits.size() > 0)
0293         ida = splits[0].accountId();
0294     if (splits.size() > 1)
0295         idb = splits[1].accountId();
0296     if (ida.isEmpty() || idb.isEmpty())
0297         return Unknown;
0298 
0299     MyMoneyAccount a, b;
0300     a = MyMoneyFile::instance()->account(ida);
0301     b = MyMoneyFile::instance()->account(idb);
0302     if ((a.accountGroup() == eMyMoney::Account::Type::Asset
0303             || a.accountGroup() == eMyMoney::Account::Type::Liability)
0304             && (b.accountGroup() == eMyMoney::Account::Type::Asset
0305                 || b.accountGroup() == eMyMoney::Account::Type::Liability))
0306         return Transfer;
0307     return Normal;
0308 }
0309 
0310 void KMyMoneyUtils::calculateAutoLoan(const MyMoneySchedule& schedule, MyMoneyTransaction& transaction, const QMap<QString, MyMoneyMoney>& balances)
0311 {
0312     try {
0313         MyMoneyForecast::calculateAutoLoan(schedule, transaction, balances);
0314     } catch (const MyMoneyException &e) {
0315         KMessageBox::detailedError(0, i18n("Unable to load schedule details"), QString::fromLatin1(e.what()));
0316     }
0317 }
0318 
0319 QString KMyMoneyUtils::nextCheckNumber(const MyMoneyAccount& acc)
0320 {
0321     return getAdjacentNumber(acc.value("lastNumberUsed"), 1);
0322 }
0323 
0324 QString KMyMoneyUtils::nextFreeCheckNumber(const MyMoneyAccount& acc)
0325 {
0326     auto file = MyMoneyFile::instance();
0327     auto num = acc.value("lastNumberUsed");
0328 
0329     if (num.isEmpty())
0330         num = QStringLiteral("1");
0331 
0332     // now check if this number has been used already
0333     if (file->checkNoUsed(acc.id(), num)) {
0334         // if a number has been entered which is immediately prior to
0335         // an existing number, the next new number produced would clash
0336         // so need to look ahead for free next number
0337         // we limit that to a number of tries which depends on the
0338         // number of splits in that account (we can't have more)
0339         MyMoneyTransactionFilter filter(acc.id());
0340         QList<MyMoneyTransaction> transactions;
0341         file->transactionList(transactions, filter);
0342         const int maxNumber = transactions.count();
0343         for (int i = 0; i < maxNumber; i++) {
0344             if (file->checkNoUsed(acc.id(), num)) {
0345                 //  increment and try again
0346                 num = getAdjacentNumber(num);
0347             } else {
0348                 //  found a free number
0349                 break;
0350             }
0351         }
0352     }
0353     return num;
0354 }
0355 
0356 QString KMyMoneyUtils::getAdjacentNumber(const QString& number, int offset)
0357 {
0358     // make sure the offset is either -1 or 1
0359     offset = (offset >= 0) ? 1 : -1;
0360 
0361     QString num = number;
0362     //                   +-#1--+ +#2++-#3-++-#4--+
0363     QRegExp exp(QString("(.*\\D)?(0*)(\\d+)(\\D.*)?"));
0364     if (exp.indexIn(num) != -1) {
0365         QString arg1 = exp.cap(1);
0366         QString arg2 = exp.cap(2);
0367         QString arg3 = QString::number(exp.cap(3).toULong() + offset);
0368         QString arg4 = exp.cap(4);
0369         num = QString("%1%2%3%4").arg(arg1, arg2, arg3, arg4);
0370     } else {
0371         num = QStringLiteral("1");
0372     }
0373     return num;
0374 }
0375 
0376 quint64 KMyMoneyUtils::numericPart(const QString & num)
0377 {
0378     quint64 num64 = 0;
0379     QRegExp exp(QString("(.*\\D)?(0*)(\\d+)(\\D.*)?"));
0380     if (exp.indexIn(num) != -1) {
0381         // QString arg1 = exp.cap(1);
0382         QString arg2 = exp.cap(2);
0383         QString arg3 = QString::number(exp.cap(3).toULongLong());
0384         // QString arg4 = exp.cap(4);
0385         num64 = QString("%2%3").arg(arg2, arg3).toULongLong();
0386     }
0387     return num64;
0388 }
0389 
0390 QString KMyMoneyUtils::reconcileStateToString(eMyMoney::Split::State flag, bool text)
0391 {
0392     QString txt;
0393     if (text) {
0394         switch (flag) {
0395         case eMyMoney::Split::State::NotReconciled:
0396             txt = i18nc("Reconciliation state 'Not reconciled'", "Not reconciled");
0397             break;
0398         case eMyMoney::Split::State::Cleared:
0399             txt = i18nc("Reconciliation state 'Cleared'", "Cleared");
0400             break;
0401         case eMyMoney::Split::State::Reconciled:
0402             txt = i18nc("Reconciliation state 'Reconciled'", "Reconciled");
0403             break;
0404         case eMyMoney::Split::State::Frozen:
0405             txt = i18nc("Reconciliation state 'Frozen'", "Frozen");
0406             break;
0407         default:
0408             txt = i18nc("Unknown reconciliation state", "Unknown");
0409             break;
0410         }
0411     } else {
0412         switch (flag) {
0413         case eMyMoney::Split::State::NotReconciled:
0414             break;
0415         case eMyMoney::Split::State::Cleared:
0416             txt = i18nc("Reconciliation flag C", "C");
0417             break;
0418         case eMyMoney::Split::State::Reconciled:
0419             txt = i18nc("Reconciliation flag R", "R");
0420             break;
0421         case eMyMoney::Split::State::Frozen:
0422             txt = i18nc("Reconciliation flag F", "F");
0423             break;
0424         default:
0425             txt = i18nc("Flag for unknown reconciliation state", "?");
0426             break;
0427         }
0428     }
0429     return txt;
0430 }
0431 
0432 MyMoneyTransaction KMyMoneyUtils::scheduledTransaction(const MyMoneySchedule& schedule)
0433 {
0434     MyMoneyTransaction t = schedule.transaction();
0435 
0436     try {
0437         if (schedule.type() == eMyMoney::Schedule::Type::LoanPayment) {
0438             calculateAutoLoan(schedule, t, QMap<QString, MyMoneyMoney>());
0439         }
0440     } catch (const MyMoneyException &e) {
0441         qDebug("Unable to load schedule details for '%s' during transaction match: %s", qPrintable(schedule.name()), e.what());
0442     }
0443 
0444     t.clearId();
0445     t.setEntryDate(QDate());
0446     return t;
0447 }
0448 
0449 KXmlGuiWindow* KMyMoneyUtils::mainWindow()
0450 {
0451     foreach (QWidget *widget, QApplication::topLevelWidgets()) {
0452         KXmlGuiWindow* result = dynamic_cast<KXmlGuiWindow*>(widget);
0453         if (result)
0454             return result;
0455     }
0456     return 0;
0457 }
0458 
0459 void KMyMoneyUtils::updateWizardButtons(QWizard* wizard)
0460 {
0461     // setup text on buttons
0462     wizard->setButtonText(QWizard::NextButton, i18nc("Go to next page of the wizard", "&Next"));
0463     wizard->setButtonText(QWizard::BackButton, KStandardGuiItem::back().text());
0464 
0465     // setup icons
0466     wizard->button(QWizard::FinishButton)->setIcon(KStandardGuiItem::ok().icon());
0467     wizard->button(QWizard::CancelButton)->setIcon(KStandardGuiItem::cancel().icon());
0468     wizard->button(QWizard::NextButton)->setIcon(KStandardGuiItem::forward(KStandardGuiItem::UseRTL).icon());
0469     wizard->button(QWizard::BackButton)->setIcon(KStandardGuiItem::back(KStandardGuiItem::UseRTL).icon());
0470 }
0471 
0472 void KMyMoneyUtils::dissectTransaction(const MyMoneyTransaction& transaction, const MyMoneySplit& split, MyMoneySplit& assetAccountSplit, QList<MyMoneySplit>& feeSplits, QList<MyMoneySplit>& interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency, eMyMoney::Split::InvestmentTransactionType& transactionType)
0473 {
0474     // collect the splits. split references the stock account and should already
0475     // be set up. assetAccountSplit references the corresponding asset account (maybe
0476     // empty), feeSplits is the list of all expenses and interestSplits
0477     // the list of all incomes
0478     assetAccountSplit = MyMoneySplit(); // set to none to check later if it was assigned
0479     auto file = MyMoneyFile::instance();
0480     foreach (const auto tsplit, transaction.splits()) {
0481         auto acc = file->account(tsplit.accountId());
0482         if (tsplit.id() == split.id()) {
0483             security = file->security(acc.currencyId());
0484         } else if (acc.accountGroup() == eMyMoney::Account::Type::Expense) {
0485             feeSplits.append(tsplit);
0486             // feeAmount += tsplit.value();
0487         } else if (acc.accountGroup() == eMyMoney::Account::Type::Income) {
0488             interestSplits.append(tsplit);
0489             // interestAmount += tsplit.value();
0490         } else {
0491             if (assetAccountSplit == MyMoneySplit()) // first asset Account should be our requested brokerage account
0492                 assetAccountSplit = tsplit;
0493             else if (tsplit.value().isNegative())  // the rest (if present) is handled as fee or interest
0494                 feeSplits.append(tsplit);              // and shouldn't be allowed to override assetAccountSplit
0495             else if (tsplit.value().isPositive())
0496                 interestSplits.append(tsplit);
0497         }
0498     }
0499 
0500     // determine transaction type
0501     if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::AddShares)) {
0502         transactionType = (!split.shares().isNegative()) ? eMyMoney::Split::InvestmentTransactionType::AddShares : eMyMoney::Split::InvestmentTransactionType::RemoveShares;
0503     } else if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::BuyShares)) {
0504         transactionType = (!split.value().isNegative()) ? eMyMoney::Split::InvestmentTransactionType::BuyShares : eMyMoney::Split::InvestmentTransactionType::SellShares;
0505     } else if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Dividend)) {
0506         transactionType = eMyMoney::Split::InvestmentTransactionType::Dividend;
0507     } else if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::ReinvestDividend)) {
0508         transactionType = eMyMoney::Split::InvestmentTransactionType::ReinvestDividend;
0509     } else if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Yield)) {
0510         transactionType = eMyMoney::Split::InvestmentTransactionType::Yield;
0511     } else if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::SplitShares)) {
0512         transactionType = eMyMoney::Split::InvestmentTransactionType::SplitShares;
0513     } else if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::InterestIncome)) {
0514         transactionType = eMyMoney::Split::InvestmentTransactionType::InterestIncome;
0515     } else
0516         transactionType = eMyMoney::Split::InvestmentTransactionType::BuyShares;
0517 
0518     currency.setTradingSymbol("???");
0519     try {
0520         currency = file->security(transaction.commodity());
0521     } catch (const MyMoneyException &) {
0522     }
0523 }
0524 
0525 void KMyMoneyUtils::processPriceList(const MyMoneyStatement &st)
0526 {
0527     auto file = MyMoneyFile::instance();
0528     QHash<QString, MyMoneySecurity> secBySymbol;
0529     QHash<QString, MyMoneySecurity> secByName;
0530 
0531     const auto securityList = file->securityList();
0532     for (const auto& sec : securityList) {
0533         secBySymbol[sec.tradingSymbol()] = sec;
0534         secByName[sec.name()] = sec;
0535     }
0536 
0537     for (const auto& stPrice : st.m_listPrices) {
0538         auto currency = file->baseCurrency().id();
0539         QString security;
0540 
0541         if (!stPrice.m_strCurrency.isEmpty()) {
0542             security = stPrice.m_strSecurity;
0543             currency = stPrice.m_strCurrency;
0544         } else if (secBySymbol.contains(stPrice.m_strSecurity)) {
0545             security = secBySymbol[stPrice.m_strSecurity].id();
0546             currency = file->security(file->security(security).tradingCurrency()).id();
0547         } else if (secByName.contains(stPrice.m_strSecurity)) {
0548             security = secByName[stPrice.m_strSecurity].id();
0549             currency = file->security(file->security(security).tradingCurrency()).id();
0550         } else
0551             return;
0552 
0553         MyMoneyPrice price(security,
0554                            currency,
0555                            stPrice.m_date,
0556                            stPrice.m_amount, stPrice.m_sourceName.isEmpty() ? i18n("Prices Importer") : stPrice.m_sourceName);
0557         file->addPrice(price);
0558     }
0559 }
0560 
0561 void KMyMoneyUtils::deleteSecurity(const MyMoneySecurity& security, QWidget* parent)
0562 {
0563     QString msg, msg2;
0564     QString dontAsk, dontAsk2;
0565     if (security.isCurrency()) {
0566         msg = i18n("<p>Do you really want to remove the currency <b>%1</b> from the file?</p>", security.name());
0567         msg2 = i18n("<p>All exchange rates for currency <b>%1</b> will be lost.</p><p>Do you still want to continue?</p>", security.name());
0568         dontAsk = "DeleteCurrency";
0569         dontAsk2 = "DeleteCurrencyRates";
0570     } else {
0571         msg = i18n("<p>Do you really want to remove the %1 <b>%2</b> from the file?</p>", MyMoneySecurity::securityTypeToString(security.securityType()), security.name());
0572         msg2 = i18n("<p>All price quotes for %1 <b>%2</b> will be lost.</p><p>Do you still want to continue?</p>", MyMoneySecurity::securityTypeToString(security.securityType()), security.name());
0573         dontAsk = "DeleteSecurity";
0574         dontAsk2 = "DeleteSecurityPrices";
0575     }
0576     if (KMessageBox::questionYesNo(parent, msg, i18n("Delete security"), KStandardGuiItem::yes(), KStandardGuiItem::no(), dontAsk) == KMessageBox::Yes) {
0577         MyMoneyFileTransaction ft;
0578         auto file = MyMoneyFile::instance();
0579 
0580         QBitArray skip((int)eStorage::Reference::Count);
0581         skip.fill(true);
0582         skip.clearBit((int)eStorage::Reference::Price);
0583         if (file->isReferenced(security, skip)) {
0584             if (KMessageBox::questionYesNo(parent, msg2, i18n("Delete prices"), KStandardGuiItem::yes(), KStandardGuiItem::no(), dontAsk2) == KMessageBox::Yes) {
0585                 try {
0586                     QString secID = security.id();
0587                     foreach (auto priceEntry, file->priceList()) {
0588                         const MyMoneyPrice& price = priceEntry.first();
0589                         if (price.from() == secID || price.to() == secID)
0590                             file->removePrice(price);
0591                     }
0592                     ft.commit();
0593                     ft.restart();
0594                 } catch (const MyMoneyException &) {
0595                     qDebug("Cannot delete price");
0596                     return;
0597                 }
0598             } else
0599                 return;
0600         }
0601         try {
0602             if (security.isCurrency())
0603                 file->removeCurrency(security);
0604             else
0605                 file->removeSecurity(security);
0606             ft.commit();
0607         } catch (const MyMoneyException &) {
0608         }
0609     }
0610 }
0611 
0612 bool KMyMoneyUtils::fileExists(const QUrl &url)
0613 {
0614     bool fileExists = false;
0615     if (url.isValid()) {
0616         if (url.isLocalFile() || url.scheme().isEmpty()) {
0617             QFileInfo check_file(url.toLocalFile());
0618             fileExists = check_file.exists() && check_file.isFile();
0619 
0620         } else {
0621 #if KIO_VERSION < QT_VERSION_CHECK(5, 70, 0)
0622             short int detailLevel = 0; // Lowest level: file/dir/symlink/none
0623             KIO::StatJob* statjob = KIO::stat(url, KIO::StatJob::SourceSide, detailLevel);
0624 #else
0625             auto statjob = KIO::statDetails(url, KIO::StatJob::SourceSide, KIO::StatNoDetails);
0626 #endif
0627             bool noerror = statjob->exec();
0628             if (noerror) {
0629                 // We want a file
0630                 fileExists = !statjob->statResult().isDir();
0631             }
0632             statjob->kill();
0633         }
0634     }
0635     return fileExists;
0636 }
0637 
0638 QString KMyMoneyUtils::downloadFile(const QUrl &url)
0639 {
0640     QString filename;
0641     KIO::StoredTransferJob *transferjob = KIO::storedGet (url);
0642 //  KJobWidgets::setWindow(transferjob, this);
0643     if (! transferjob->exec()) {
0644         KMessageBox::detailedError(nullptr,
0645                                    i18n("Error while loading file '%1'.", url.url()),
0646                                    transferjob->errorString(),
0647                                    i18n("File access error"));
0648         return filename;
0649     }
0650 
0651     QTemporaryFile file;
0652     file.setAutoRemove(false);
0653     file.open();
0654     file.write(transferjob->data());
0655     filename = file.fileName();
0656     file.close();
0657     return filename;
0658 }
0659 
0660 bool KMyMoneyUtils::newPayee(const QString& newnameBase, QString& id)
0661 {
0662     bool doit = true;
0663 
0664     if (newnameBase != i18n("New Payee")) {
0665         // Ask the user if that is what he intended to do?
0666         const auto msg = i18n("<qt>Do you want to add <b>%1</b> as payer/receiver?</qt>", newnameBase);
0667 
0668         if (KMessageBox::questionYesNo(nullptr, msg, i18n("New payee/receiver"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "NewPayee") == KMessageBox::No) {
0669             doit = false;
0670             // we should not keep the 'no' setting because that can confuse people like
0671             // I have seen in some usability tests. So we just delete it right away.
0672             KSharedConfigPtr kconfig = KSharedConfig::openConfig();
0673             if (kconfig) {
0674                 kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("NewPayee"));
0675             }
0676         }
0677     }
0678 
0679     if (doit) {
0680         MyMoneyFileTransaction ft;
0681         try {
0682             QString newname(newnameBase);
0683             // adjust name until a unique name has been created
0684             int count = 0;
0685             for (;;) {
0686                 try {
0687                     MyMoneyFile::instance()->payeeByName(newname);
0688                     newname = QString::fromLatin1("%1 [%2]").arg(newnameBase).arg(++count);
0689                 } catch (const MyMoneyException &) {
0690                     break;
0691                 }
0692             }
0693 
0694             MyMoneyPayee p;
0695             p.setName(newname);
0696             p.setMatchData(eMyMoney::Payee::MatchType::NameExact, true, QStringList());
0697             MyMoneyFile::instance()->addPayee(p);
0698             id = p.id();
0699             ft.commit();
0700         } catch (const MyMoneyException &e) {
0701             KMessageBox::detailedSorry(nullptr, i18n("Unable to add payee"), QString::fromLatin1(e.what()));
0702             doit = false;
0703         }
0704     }
0705     return doit;
0706 }
0707 
0708 void KMyMoneyUtils::newTag(const QString& newnameBase, QString& id)
0709 {
0710     bool doit = true;
0711 
0712     if (newnameBase != i18n("New Tag")) {
0713         // Ask the user if that is what he intended to do?
0714         const auto msg = i18n("<qt>Do you want to add <b>%1</b> as tag?</qt>", newnameBase);
0715 
0716         if (KMessageBox::questionYesNo(nullptr, msg, i18n("New tag"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "NewTag") == KMessageBox::No) {
0717             doit = false;
0718             // we should not keep the 'no' setting because that can confuse people like
0719             // I have seen in some usability tests. So we just delete it right away.
0720             KSharedConfigPtr kconfig = KSharedConfig::openConfig();
0721             if (kconfig) {
0722                 kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("NewTag"));
0723             }
0724         }
0725     }
0726 
0727     if (doit) {
0728         MyMoneyFileTransaction ft;
0729         try {
0730             QString newname(newnameBase);
0731             // adjust name until a unique name has been created
0732             int count = 0;
0733             for (;;) {
0734                 try {
0735                     MyMoneyFile::instance()->tagByName(newname);
0736                     newname = QString::fromLatin1("%1 [%2]").arg(newnameBase).arg(++count);
0737                 } catch (const MyMoneyException &) {
0738                     break;
0739                 }
0740             }
0741 
0742             MyMoneyTag ta;
0743             ta.setName(newname);
0744             MyMoneyFile::instance()->addTag(ta);
0745             id = ta.id();
0746             ft.commit();
0747         } catch (const MyMoneyException &e) {
0748             KMessageBox::detailedSorry(nullptr, i18n("Unable to add tag"), QString::fromLatin1(e.what()));
0749         }
0750     }
0751 }
0752 
0753 void KMyMoneyUtils::newInstitution(MyMoneyInstitution& institution)
0754 {
0755     auto file = MyMoneyFile::instance();
0756 
0757     MyMoneyFileTransaction ft;
0758 
0759     try {
0760         file->addInstitution(institution);
0761         ft.commit();
0762 
0763     } catch (const MyMoneyException &e) {
0764         KMessageBox::information(nullptr, i18n("Cannot add institution: %1", QString::fromLatin1(e.what())));
0765     }
0766 }
0767 
0768 QDebug KMyMoneyUtils::debug()
0769 {
0770     return qDebug() << QDateTime::currentDateTime().toString(QStringLiteral("HH:mm:ss.zzz"));
0771 }
0772 
0773 MyMoneyForecast KMyMoneyUtils::forecast()
0774 {
0775     MyMoneyForecast forecast;
0776 
0777     // override object defaults with those of the application
0778     forecast.setForecastCycles(KMyMoneySettings::forecastCycles());
0779     forecast.setAccountsCycle(KMyMoneySettings::forecastAccountCycle());
0780     forecast.setHistoryStartDate(QDate::currentDate().addDays(-forecast.forecastCycles()*forecast.accountsCycle()));
0781     forecast.setHistoryEndDate(QDate::currentDate().addDays(-1));
0782     forecast.setForecastDays(KMyMoneySettings::forecastDays());
0783     forecast.setBeginForecastDay(KMyMoneySettings::beginForecastDay());
0784     forecast.setForecastMethod(KMyMoneySettings::forecastMethod());
0785     forecast.setHistoryMethod(KMyMoneySettings::historyMethod());
0786     forecast.setIncludeFutureTransactions(KMyMoneySettings::includeFutureTransactions());
0787     forecast.setIncludeScheduledTransactions(KMyMoneySettings::includeScheduledTransactions());
0788 
0789     return forecast;
0790 }
0791 
0792 bool KMyMoneyUtils::canUpdateAllAccounts()
0793 {
0794     const auto file = MyMoneyFile::instance();
0795     auto rc = false;
0796     if (!file->storageAttached())
0797         return rc;
0798 
0799     QList<MyMoneyAccount> accList;
0800     file->accountList(accList);
0801     QList<MyMoneyAccount>::const_iterator it_a;
0802     auto it_p = pPlugins.online.constEnd();
0803     for (it_a = accList.constBegin(); (it_p == pPlugins.online.constEnd()) && (it_a != accList.constEnd()); ++it_a) {
0804         if ((*it_a).hasOnlineMapping()) {
0805             // check if provider is available
0806             it_p = pPlugins.online.constFind((*it_a).onlineBankingSettings().value("provider").toLower());
0807             if (it_p != pPlugins.online.constEnd()) {
0808                 QStringList protocols;
0809                 (*it_p)->protocols(protocols);
0810                 if (!protocols.isEmpty()) {
0811                     rc = true;
0812                     break;
0813                 }
0814             }
0815         }
0816     }
0817     return rc;
0818 }
0819 
0820 void KMyMoneyUtils::showStatementImportResult(const QStringList& resultMessages, uint statementCount)
0821 {
0822     KMessageBox::informationList(nullptr,
0823                                  i18np("One statement has been processed with the following results:",
0824                                        "%1 statements have been processed with the following results:",
0825                                        statementCount),
0826                                  !resultMessages.isEmpty() ?
0827                                  resultMessages :
0828                                  QStringList { i18np("No new transaction has been imported.", "No new transactions have been imported.", statementCount) },
0829                                  i18n("Statement import statistics"));
0830 }
0831 
0832 QString KMyMoneyUtils::normalizeNumericString(const qreal& val, const QLocale& loc, const char f, const int prec)
0833 {
0834     return loc.toString(val, f, prec)
0835            .remove(loc.groupSeparator())
0836            .remove(QRegularExpression("0+$"))
0837            .remove(QRegularExpression("\\" + loc.decimalPoint() + "$"));
0838 }