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 }