1 /***************************************************************************
2 kmymoneyutils.cpp - description
3 -------------------
4 begin : Wed Feb 5 2003
5 copyright : (C) 2000-2003 by Michael Edwardes
6 email : mte@users.sourceforge.net
7 Javier Campos Morales <javi_c@users.sourceforge.net>
8 Felix Rodriguez <frodriguez@users.sourceforge.net>
9 John C <thetacoturtle@users.sourceforge.net>
10 Thomas Baumgart <ipwizard@users.sourceforge.net>
11 Kevin Tambascio <ktambascio@users.sourceforge.net>
12 (C) 2017 by Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
13 ***************************************************************************/
14
15 /***************************************************************************
16 * *
17 * This program is free software; you can redistribute it and/or modify *
18 * it under the terms of the GNU General Public License as published by *
19 * the Free Software Foundation; either version 2 of the License, or *
20 * (at your option) any later version. *
21 * *
22 ***************************************************************************/
23
24 #include "kmymoneyutils.h"
25
26 // ----------------------------------------------------------------------------
27 // QT Includes
28
29 #include <QWidget>
30 #include <QApplication>
31 #include <QList>
32 #include <QPixmap>
33 #include <QWizard>
34 #include <QAbstractButton>
35 #include <QPixmapCache>
36 #include <QIcon>
37 #include <QPainter>
38 #include <QBitArray>
39 #include <QRegularExpression>
40 #include <QRegularExpressionMatch>
41 #include <QTemporaryFile>
42 #include <QFileInfo>
43
44 // ----------------------------------------------------------------------------
45 // KDE Headers
46
47 #include <KColorScheme>
48 #include <KLocalizedString>
49 #include <KGuiItem>
50 #include <KXmlGuiWindow>
51 #include <KMessageBox>
52 #include <KStandardGuiItem>
53 #include <KIO/StatJob>
54 #include <KIO/StoredTransferJob>
55
56 // ----------------------------------------------------------------------------
57 // Project Includes
58
59 #include "mymoneymoney.h"
60 #include "mymoneyexception.h"
61 #include "mymoneytransactionfilter.h"
62 #include "mymoneyfile.h"
63 #include "mymoneyaccount.h"
64 #include "mymoneysecurity.h"
65 #include "mymoneyschedule.h"
66 #include "mymoneypayee.h"
67 #include "mymoneytag.h"
68 #include "mymoneyprice.h"
69 #include "mymoneystatement.h"
70 #include "mymoneyforecast.h"
71 #include "mymoneysplit.h"
72 #include "mymoneytransaction.h"
73 #include "kmymoneysettings.h"
74 #include "icons.h"
75 #include "storageenums.h"
76 #include "mymoneyenums.h"
77 #include "kmymoneyplugin.h"
78
79 using namespace Icons;
80
KMyMoneyUtils()81 KMyMoneyUtils::KMyMoneyUtils()
82 {
83 }
84
~KMyMoneyUtils()85 KMyMoneyUtils::~KMyMoneyUtils()
86 {
87 }
88
occurrenceToString(const eMyMoney::Schedule::Occurrence occurrence)89 const QString KMyMoneyUtils::occurrenceToString(const eMyMoney::Schedule::Occurrence occurrence)
90 {
91 return i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(occurrence).toLatin1());
92 }
93
paymentMethodToString(eMyMoney::Schedule::PaymentType paymentType)94 const QString KMyMoneyUtils::paymentMethodToString(eMyMoney::Schedule::PaymentType paymentType)
95 {
96 return i18nc("Scheduled Transaction payment type", MyMoneySchedule::paymentMethodToString(paymentType).toLatin1());
97 }
98
weekendOptionToString(eMyMoney::Schedule::WeekendOption weekendOption)99 const QString KMyMoneyUtils::weekendOptionToString(eMyMoney::Schedule::WeekendOption weekendOption)
100 {
101 return i18n(MyMoneySchedule::weekendOptionToString(weekendOption).toLatin1());
102 }
103
scheduleTypeToString(eMyMoney::Schedule::Type type)104 const QString KMyMoneyUtils::scheduleTypeToString(eMyMoney::Schedule::Type type)
105 {
106 return i18nc("Scheduled transaction type", MyMoneySchedule::scheduleTypeToString(type).toLatin1());
107 }
108
scheduleNewGuiItem()109 KGuiItem KMyMoneyUtils::scheduleNewGuiItem()
110 {
111 KGuiItem splitGuiItem(i18n("&New Schedule..."),
112 Icons::get(Icon::DocumentNew),
113 i18n("Create a new schedule."),
114 i18n("Use this to create a new schedule."));
115
116 return splitGuiItem;
117 }
118
accountsFilterGuiItem()119 KGuiItem KMyMoneyUtils::accountsFilterGuiItem()
120 {
121 KGuiItem splitGuiItem(i18n("&Filter"),
122 Icons::get(Icon::Filter),
123 i18n("Filter out accounts"),
124 i18n("Use this to filter out accounts"));
125
126 return splitGuiItem;
127 }
128
129 const char* homePageItems[] = {
130 I18N_NOOP("Payments"),
131 I18N_NOOP("Preferred accounts"),
132 I18N_NOOP("Payment accounts"),
133 I18N_NOOP("Favorite reports"),
134 I18N_NOOP("Forecast (schedule)"),
135 I18N_NOOP("Net worth forecast"),
136 I18N_NOOP("Forecast (history)"), // unused, s.a. KSettingsHome::slotLoadItems()
137 I18N_NOOP("Assets and Liabilities"),
138 I18N_NOOP("Budget"),
139 I18N_NOOP("CashFlow"),
140 // insert new items above this comment
141 0
142 };
143
homePageItemToString(const int idx)144 const QString KMyMoneyUtils::homePageItemToString(const int idx)
145 {
146 QString rc;
147 if (abs(idx) > 0 && abs(idx) < static_cast<int>(sizeof(homePageItems) / sizeof(homePageItems[0]))) {
148 rc = i18n(homePageItems[abs(idx-1)]);
149 }
150 return rc;
151 }
152
stringToHomePageItem(const QString & txt)153 int KMyMoneyUtils::stringToHomePageItem(const QString& txt)
154 {
155 int idx = 0;
156 for (idx = 0; homePageItems[idx] != 0; ++idx) {
157 if (txt == i18n(homePageItems[idx]))
158 return idx + 1;
159 }
160 return 0;
161 }
162
appendCorrectFileExt(QString & str,const QString & strExtToUse)163 bool KMyMoneyUtils::appendCorrectFileExt(QString& str, const QString& strExtToUse)
164 {
165 bool rc = false;
166
167 if (!str.isEmpty()) {
168 //find last . deliminator
169 int nLoc = str.lastIndexOf('.');
170 if (nLoc != -1) {
171 QString strExt, strTemp;
172 strTemp = str.left(nLoc + 1);
173 strExt = str.right(str.length() - (nLoc + 1));
174 if (strExt.indexOf(strExtToUse, 0, Qt::CaseInsensitive) == -1) {
175 // if the extension given contains a period, we remove ours
176 if (strExtToUse.indexOf('.') != -1)
177 strTemp = strTemp.left(strTemp.length() - 1);
178 //append extension to make complete file name
179 strTemp.append(strExtToUse);
180 str = strTemp;
181 rc = true;
182 }
183 } else {
184 str.append(QLatin1Char('.'));
185 str.append(strExtToUse);
186 rc = true;
187 }
188 }
189 return rc;
190 }
191
checkConstants()192 void KMyMoneyUtils::checkConstants()
193 {
194 // TODO: port to kf5
195 #if 0
196 Q_ASSERT(static_cast<int>(KLocale::ParensAround) == static_cast<int>(MyMoneyMoney::ParensAround));
197 Q_ASSERT(static_cast<int>(KLocale::BeforeQuantityMoney) == static_cast<int>(MyMoneyMoney::BeforeQuantityMoney));
198 Q_ASSERT(static_cast<int>(KLocale::AfterQuantityMoney) == static_cast<int>(MyMoneyMoney::AfterQuantityMoney));
199 Q_ASSERT(static_cast<int>(KLocale::BeforeMoney) == static_cast<int>(MyMoneyMoney::BeforeMoney));
200 Q_ASSERT(static_cast<int>(KLocale::AfterMoney) == static_cast<int>(MyMoneyMoney::AfterMoney));
201 #endif
202 }
203
variableCSS()204 QString KMyMoneyUtils::variableCSS()
205 {
206 QColor tcolor = KColorScheme(QPalette::Active).foreground(KColorScheme::NormalText).color();
207 QColor link = KColorScheme(QPalette::Active).foreground(KColorScheme::LinkText).color();
208
209 QString css;
210 css += "<style type=\"text/css\">\n<!--\n";
211 css += QString(".row-even, .item0 { background-color: %1; color: %2 }\n")
212 .arg(KMyMoneySettings::schemeColor(SchemeColor::ListBackground1).name()).arg(tcolor.name());
213 css += QString(".row-odd, .item1 { background-color: %1; color: %2 }\n")
214 .arg(KMyMoneySettings::schemeColor(SchemeColor::ListBackground2).name()).arg(tcolor.name());
215 css += QString("a { color: %1 }\n").arg(link.name());
216 css += "-->\n</style>\n";
217 return css;
218 }
219
findResource(QStandardPaths::StandardLocation type,const QString & filename)220 QString KMyMoneyUtils::findResource(QStandardPaths::StandardLocation type, const QString& filename)
221 {
222 QLocale locale;
223 QString country;
224 QString localeName = locale.bcp47Name();
225 QString language = localeName;
226
227 // extract language and country from the bcp47name
228 QRegularExpression regExp(QLatin1String("(\\w+)_(\\w+)"));
229 QRegularExpressionMatch match = regExp.match(localeName);
230 if (match.hasMatch()) {
231 language = match.captured(1);
232 country = match.captured(2);
233 }
234
235 QString rc;
236
237 // check that the placeholder is present and set things up
238 if (filename.indexOf("%1") != -1) {
239 /// @fixme somehow I have the impression, that language and country
240 /// mappings to the filename are not correct. This certainly must
241 /// be overhauled at some point in time (ipwizard, 2017-10-22)
242 QString mask = filename.arg("_%1.%2");
243 rc = QStandardPaths::locate(type, mask.arg(country, language));
244
245 // search the given resource
246 if (rc.isEmpty()) {
247 mask = filename.arg("_%1");
248 rc = QStandardPaths::locate(type, mask.arg(language));
249 }
250 if (rc.isEmpty()) {
251 // qDebug(QString("html/home_%1.html not found").arg(country).toLatin1());
252 rc = QStandardPaths::locate(type, mask.arg(country));
253 }
254 if (rc.isEmpty()) {
255 rc = QStandardPaths::locate(type, filename.arg(""));
256 }
257 } else {
258 rc = QStandardPaths::locate(type, filename);
259 }
260
261 if (rc.isEmpty()) {
262 qWarning("No resource found for (%s,%s)", qPrintable(QStandardPaths::displayName(type)), qPrintable(filename));
263 }
264 return rc;
265 }
266
stockSplit(const MyMoneyTransaction & t)267 const MyMoneySplit KMyMoneyUtils::stockSplit(const MyMoneyTransaction& t)
268 {
269 MyMoneySplit investmentAccountSplit;
270 foreach (const auto split, t.splits()) {
271 if (!split.accountId().isEmpty()) {
272 auto acc = MyMoneyFile::instance()->account(split.accountId());
273 if (acc.isInvest()) {
274 return split;
275 }
276 // if we have a reference to an investment account, we remember it here
277 if (acc.accountType() == eMyMoney::Account::Type::Investment)
278 investmentAccountSplit = split;
279 }
280 }
281 // if we haven't found a stock split, we see if we've seen
282 // an investment account on the way. If so, we return it.
283 if (!investmentAccountSplit.id().isEmpty())
284 return investmentAccountSplit;
285
286 // if none was found, we return an empty split.
287 return MyMoneySplit();
288 }
289
transactionType(const MyMoneyTransaction & t)290 KMyMoneyUtils::transactionTypeE KMyMoneyUtils::transactionType(const MyMoneyTransaction& t)
291 {
292 if (!stockSplit(t).id().isEmpty())
293 return InvestmentTransaction;
294
295 if (t.splitCount() < 2) {
296 return Unknown;
297 } else if (t.splitCount() > 2) {
298 // FIXME check for loan transaction here
299 return SplitTransaction;
300 }
301 QString ida, idb;
302 const auto & splits = t.splits();
303 if (splits.size() > 0)
304 ida = splits[0].accountId();
305 if (splits.size() > 1)
306 idb = splits[1].accountId();
307 if (ida.isEmpty() || idb.isEmpty())
308 return Unknown;
309
310 MyMoneyAccount a, b;
311 a = MyMoneyFile::instance()->account(ida);
312 b = MyMoneyFile::instance()->account(idb);
313 if ((a.accountGroup() == eMyMoney::Account::Type::Asset
314 || a.accountGroup() == eMyMoney::Account::Type::Liability)
315 && (b.accountGroup() == eMyMoney::Account::Type::Asset
316 || b.accountGroup() == eMyMoney::Account::Type::Liability))
317 return Transfer;
318 return Normal;
319 }
320
calculateAutoLoan(const MyMoneySchedule & schedule,MyMoneyTransaction & transaction,const QMap<QString,MyMoneyMoney> & balances)321 void KMyMoneyUtils::calculateAutoLoan(const MyMoneySchedule& schedule, MyMoneyTransaction& transaction, const QMap<QString, MyMoneyMoney>& balances)
322 {
323 try {
324 MyMoneyForecast::calculateAutoLoan(schedule, transaction, balances);
325 } catch (const MyMoneyException &e) {
326 KMessageBox::detailedError(0, i18n("Unable to load schedule details"), QString::fromLatin1(e.what()));
327 }
328 }
329
nextCheckNumber(const MyMoneyAccount & acc)330 QString KMyMoneyUtils::nextCheckNumber(const MyMoneyAccount& acc)
331 {
332 return getAdjacentNumber(acc.value("lastNumberUsed"), 1);
333 }
334
nextFreeCheckNumber(const MyMoneyAccount & acc)335 QString KMyMoneyUtils::nextFreeCheckNumber(const MyMoneyAccount& acc)
336 {
337 auto file = MyMoneyFile::instance();
338 auto num = acc.value("lastNumberUsed");
339
340 if (num.isEmpty())
341 num = QStringLiteral("1");
342
343 // now check if this number has been used already
344 if (file->checkNoUsed(acc.id(), num)) {
345 // if a number has been entered which is immediately prior to
346 // an existing number, the next new number produced would clash
347 // so need to look ahead for free next number
348 // we limit that to a number of tries which depends on the
349 // number of splits in that account (we can't have more)
350 MyMoneyTransactionFilter filter(acc.id());
351 QList<MyMoneyTransaction> transactions;
352 file->transactionList(transactions, filter);
353 const int maxNumber = transactions.count();
354 for (int i = 0; i < maxNumber; i++) {
355 if (file->checkNoUsed(acc.id(), num)) {
356 // increment and try again
357 num = getAdjacentNumber(num);
358 } else {
359 // found a free number
360 break;
361 }
362 }
363 }
364 return num;
365 }
366
getAdjacentNumber(const QString & number,int offset)367 QString KMyMoneyUtils::getAdjacentNumber(const QString& number, int offset)
368 {
369 // make sure the offset is either -1 or 1
370 offset = (offset >= 0) ? 1 : -1;
371
372 QString num = number;
373 // +-#1--+ +#2++-#3-++-#4--+
374 QRegExp exp(QString("(.*\\D)?(0*)(\\d+)(\\D.*)?"));
375 if (exp.indexIn(num) != -1) {
376 QString arg1 = exp.cap(1);
377 QString arg2 = exp.cap(2);
378 QString arg3 = QString::number(exp.cap(3).toULong() + offset);
379 QString arg4 = exp.cap(4);
380 num = QString("%1%2%3%4").arg(arg1, arg2, arg3, arg4);
381 } else {
382 num = QStringLiteral("1");
383 }
384 return num;
385 }
386
numericPart(const QString & num)387 quint64 KMyMoneyUtils::numericPart(const QString & num)
388 {
389 quint64 num64 = 0;
390 QRegExp exp(QString("(.*\\D)?(0*)(\\d+)(\\D.*)?"));
391 if (exp.indexIn(num) != -1) {
392 // QString arg1 = exp.cap(1);
393 QString arg2 = exp.cap(2);
394 QString arg3 = QString::number(exp.cap(3).toULongLong());
395 // QString arg4 = exp.cap(4);
396 num64 = QString("%2%3").arg(arg2, arg3).toULongLong();
397 }
398 return num64;
399 }
400
reconcileStateToString(eMyMoney::Split::State flag,bool text)401 QString KMyMoneyUtils::reconcileStateToString(eMyMoney::Split::State flag, bool text)
402 {
403 QString txt;
404 if (text) {
405 switch (flag) {
406 case eMyMoney::Split::State::NotReconciled:
407 txt = i18nc("Reconciliation state 'Not reconciled'", "Not reconciled");
408 break;
409 case eMyMoney::Split::State::Cleared:
410 txt = i18nc("Reconciliation state 'Cleared'", "Cleared");
411 break;
412 case eMyMoney::Split::State::Reconciled:
413 txt = i18nc("Reconciliation state 'Reconciled'", "Reconciled");
414 break;
415 case eMyMoney::Split::State::Frozen:
416 txt = i18nc("Reconciliation state 'Frozen'", "Frozen");
417 break;
418 default:
419 txt = i18nc("Unknown reconciliation state", "Unknown");
420 break;
421 }
422 } else {
423 switch (flag) {
424 case eMyMoney::Split::State::NotReconciled:
425 break;
426 case eMyMoney::Split::State::Cleared:
427 txt = i18nc("Reconciliation flag C", "C");
428 break;
429 case eMyMoney::Split::State::Reconciled:
430 txt = i18nc("Reconciliation flag R", "R");
431 break;
432 case eMyMoney::Split::State::Frozen:
433 txt = i18nc("Reconciliation flag F", "F");
434 break;
435 default:
436 txt = i18nc("Flag for unknown reconciliation state", "?");
437 break;
438 }
439 }
440 return txt;
441 }
442
scheduledTransaction(const MyMoneySchedule & schedule)443 MyMoneyTransaction KMyMoneyUtils::scheduledTransaction(const MyMoneySchedule& schedule)
444 {
445 MyMoneyTransaction t = schedule.transaction();
446
447 try {
448 if (schedule.type() == eMyMoney::Schedule::Type::LoanPayment) {
449 calculateAutoLoan(schedule, t, QMap<QString, MyMoneyMoney>());
450 }
451 } catch (const MyMoneyException &e) {
452 qDebug("Unable to load schedule details for '%s' during transaction match: %s", qPrintable(schedule.name()), e.what());
453 }
454
455 t.clearId();
456 t.setEntryDate(QDate());
457 return t;
458 }
459
mainWindow()460 KXmlGuiWindow* KMyMoneyUtils::mainWindow()
461 {
462 foreach (QWidget *widget, QApplication::topLevelWidgets()) {
463 KXmlGuiWindow* result = dynamic_cast<KXmlGuiWindow*>(widget);
464 if (result)
465 return result;
466 }
467 return 0;
468 }
469
updateWizardButtons(QWizard * wizard)470 void KMyMoneyUtils::updateWizardButtons(QWizard* wizard)
471 {
472 // setup text on buttons
473 wizard->setButtonText(QWizard::NextButton, i18nc("Go to next page of the wizard", "&Next"));
474 wizard->setButtonText(QWizard::BackButton, KStandardGuiItem::back().text());
475
476 // setup icons
477 wizard->button(QWizard::FinishButton)->setIcon(KStandardGuiItem::ok().icon());
478 wizard->button(QWizard::CancelButton)->setIcon(KStandardGuiItem::cancel().icon());
479 wizard->button(QWizard::NextButton)->setIcon(KStandardGuiItem::forward(KStandardGuiItem::UseRTL).icon());
480 wizard->button(QWizard::BackButton)->setIcon(KStandardGuiItem::back(KStandardGuiItem::UseRTL).icon());
481 }
482
dissectTransaction(const MyMoneyTransaction & transaction,const MyMoneySplit & split,MyMoneySplit & assetAccountSplit,QList<MyMoneySplit> & feeSplits,QList<MyMoneySplit> & interestSplits,MyMoneySecurity & security,MyMoneySecurity & currency,eMyMoney::Split::InvestmentTransactionType & transactionType)483 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)
484 {
485 // collect the splits. split references the stock account and should already
486 // be set up. assetAccountSplit references the corresponding asset account (maybe
487 // empty), feeSplits is the list of all expenses and interestSplits
488 // the list of all incomes
489 assetAccountSplit = MyMoneySplit(); // set to none to check later if it was assigned
490 auto file = MyMoneyFile::instance();
491 foreach (const auto tsplit, transaction.splits()) {
492 auto acc = file->account(tsplit.accountId());
493 if (tsplit.id() == split.id()) {
494 security = file->security(acc.currencyId());
495 } else if (acc.accountGroup() == eMyMoney::Account::Type::Expense) {
496 feeSplits.append(tsplit);
497 // feeAmount += tsplit.value();
498 } else if (acc.accountGroup() == eMyMoney::Account::Type::Income) {
499 interestSplits.append(tsplit);
500 // interestAmount += tsplit.value();
501 } else {
502 if (assetAccountSplit == MyMoneySplit()) // first asset Account should be our requested brokerage account
503 assetAccountSplit = tsplit;
504 else if (tsplit.value().isNegative()) // the rest (if present) is handled as fee or interest
505 feeSplits.append(tsplit); // and shouldn't be allowed to override assetAccountSplit
506 else if (tsplit.value().isPositive())
507 interestSplits.append(tsplit);
508 }
509 }
510
511 // determine transaction type
512 if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::AddShares)) {
513 transactionType = (!split.shares().isNegative()) ? eMyMoney::Split::InvestmentTransactionType::AddShares : eMyMoney::Split::InvestmentTransactionType::RemoveShares;
514 } else if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::BuyShares)) {
515 transactionType = (!split.value().isNegative()) ? eMyMoney::Split::InvestmentTransactionType::BuyShares : eMyMoney::Split::InvestmentTransactionType::SellShares;
516 } else if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Dividend)) {
517 transactionType = eMyMoney::Split::InvestmentTransactionType::Dividend;
518 } else if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::ReinvestDividend)) {
519 transactionType = eMyMoney::Split::InvestmentTransactionType::ReinvestDividend;
520 } else if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Yield)) {
521 transactionType = eMyMoney::Split::InvestmentTransactionType::Yield;
522 } else if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::SplitShares)) {
523 transactionType = eMyMoney::Split::InvestmentTransactionType::SplitShares;
524 } else if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::InterestIncome)) {
525 transactionType = eMyMoney::Split::InvestmentTransactionType::InterestIncome;
526 } else
527 transactionType = eMyMoney::Split::InvestmentTransactionType::BuyShares;
528
529 currency.setTradingSymbol("???");
530 try {
531 currency = file->security(transaction.commodity());
532 } catch (const MyMoneyException &) {
533 }
534 }
535
processPriceList(const MyMoneyStatement & st)536 void KMyMoneyUtils::processPriceList(const MyMoneyStatement &st)
537 {
538 auto file = MyMoneyFile::instance();
539 QHash<QString, MyMoneySecurity> secBySymbol;
540 QHash<QString, MyMoneySecurity> secByName;
541
542 const auto securityList = file->securityList();
543 for (const auto& sec : securityList) {
544 secBySymbol[sec.tradingSymbol()] = sec;
545 secByName[sec.name()] = sec;
546 }
547
548 for (const auto& stPrice : st.m_listPrices) {
549 auto currency = file->baseCurrency().id();
550 QString security;
551
552 if (!stPrice.m_strCurrency.isEmpty()) {
553 security = stPrice.m_strSecurity;
554 currency = stPrice.m_strCurrency;
555 } else if (secBySymbol.contains(stPrice.m_strSecurity)) {
556 security = secBySymbol[stPrice.m_strSecurity].id();
557 currency = file->security(file->security(security).tradingCurrency()).id();
558 } else if (secByName.contains(stPrice.m_strSecurity)) {
559 security = secByName[stPrice.m_strSecurity].id();
560 currency = file->security(file->security(security).tradingCurrency()).id();
561 } else
562 return;
563
564 MyMoneyPrice price(security,
565 currency,
566 stPrice.m_date,
567 stPrice.m_amount, stPrice.m_sourceName.isEmpty() ? i18n("Prices Importer") : stPrice.m_sourceName);
568 file->addPrice(price);
569 }
570 }
571
deleteSecurity(const MyMoneySecurity & security,QWidget * parent)572 void KMyMoneyUtils::deleteSecurity(const MyMoneySecurity& security, QWidget* parent)
573 {
574 QString msg, msg2;
575 QString dontAsk, dontAsk2;
576 if (security.isCurrency()) {
577 msg = i18n("<p>Do you really want to remove the currency <b>%1</b> from the file?</p>", security.name());
578 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());
579 dontAsk = "DeleteCurrency";
580 dontAsk2 = "DeleteCurrencyRates";
581 } else {
582 msg = i18n("<p>Do you really want to remove the %1 <b>%2</b> from the file?</p>", MyMoneySecurity::securityTypeToString(security.securityType()), security.name());
583 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());
584 dontAsk = "DeleteSecurity";
585 dontAsk2 = "DeleteSecurityPrices";
586 }
587 if (KMessageBox::questionYesNo(parent, msg, i18n("Delete security"), KStandardGuiItem::yes(), KStandardGuiItem::no(), dontAsk) == KMessageBox::Yes) {
588 MyMoneyFileTransaction ft;
589 auto file = MyMoneyFile::instance();
590
591 QBitArray skip((int)eStorage::Reference::Count);
592 skip.fill(true);
593 skip.clearBit((int)eStorage::Reference::Price);
594 if (file->isReferenced(security, skip)) {
595 if (KMessageBox::questionYesNo(parent, msg2, i18n("Delete prices"), KStandardGuiItem::yes(), KStandardGuiItem::no(), dontAsk2) == KMessageBox::Yes) {
596 try {
597 QString secID = security.id();
598 foreach (auto priceEntry, file->priceList()) {
599 const MyMoneyPrice& price = priceEntry.first();
600 if (price.from() == secID || price.to() == secID)
601 file->removePrice(price);
602 }
603 ft.commit();
604 ft.restart();
605 } catch (const MyMoneyException &) {
606 qDebug("Cannot delete price");
607 return;
608 }
609 } else
610 return;
611 }
612 try {
613 if (security.isCurrency())
614 file->removeCurrency(security);
615 else
616 file->removeSecurity(security);
617 ft.commit();
618 } catch (const MyMoneyException &) {
619 }
620 }
621 }
622
fileExists(const QUrl & url)623 bool KMyMoneyUtils::fileExists(const QUrl &url)
624 {
625 bool fileExists = false;
626 if (url.isValid()) {
627 if (url.isLocalFile() || url.scheme().isEmpty()) {
628 QFileInfo check_file(url.toLocalFile());
629 fileExists = check_file.exists() && check_file.isFile();
630
631 } else {
632 short int detailLevel = 0; // Lowest level: file/dir/symlink/none
633 KIO::StatJob* statjob = KIO::stat(url, KIO::StatJob::SourceSide, detailLevel);
634 bool noerror = statjob->exec();
635 if (noerror) {
636 // We want a file
637 fileExists = !statjob->statResult().isDir();
638 }
639 statjob->kill();
640 }
641 }
642 return fileExists;
643 }
644
downloadFile(const QUrl & url)645 QString KMyMoneyUtils::downloadFile(const QUrl &url)
646 {
647 QString filename;
648 KIO::StoredTransferJob *transferjob = KIO::storedGet (url);
649 // KJobWidgets::setWindow(transferjob, this);
650 if (! transferjob->exec()) {
651 KMessageBox::detailedError(nullptr,
652 i18n("Error while loading file '%1'.", url.url()),
653 transferjob->errorString(),
654 i18n("File access error"));
655 return filename;
656 }
657
658 QTemporaryFile file;
659 file.setAutoRemove(false);
660 file.open();
661 file.write(transferjob->data());
662 filename = file.fileName();
663 file.close();
664 return filename;
665 }
666
newPayee(const QString & newnameBase,QString & id)667 bool KMyMoneyUtils::newPayee(const QString& newnameBase, QString& id)
668 {
669 bool doit = true;
670
671 if (newnameBase != i18n("New Payee")) {
672 // Ask the user if that is what he intended to do?
673 const auto msg = i18n("<qt>Do you want to add <b>%1</b> as payer/receiver?</qt>", newnameBase);
674
675 if (KMessageBox::questionYesNo(nullptr, msg, i18n("New payee/receiver"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "NewPayee") == KMessageBox::No) {
676 doit = false;
677 // we should not keep the 'no' setting because that can confuse people like
678 // I have seen in some usability tests. So we just delete it right away.
679 KSharedConfigPtr kconfig = KSharedConfig::openConfig();
680 if (kconfig) {
681 kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("NewPayee"));
682 }
683 }
684 }
685
686 if (doit) {
687 MyMoneyFileTransaction ft;
688 try {
689 QString newname(newnameBase);
690 // adjust name until a unique name has been created
691 int count = 0;
692 for (;;) {
693 try {
694 MyMoneyFile::instance()->payeeByName(newname);
695 newname = QString::fromLatin1("%1 [%2]").arg(newnameBase).arg(++count);
696 } catch (const MyMoneyException &) {
697 break;
698 }
699 }
700
701 MyMoneyPayee p;
702 p.setName(newname);
703 MyMoneyFile::instance()->addPayee(p);
704 id = p.id();
705 ft.commit();
706 } catch (const MyMoneyException &e) {
707 KMessageBox::detailedSorry(nullptr, i18n("Unable to add payee"), QString::fromLatin1(e.what()));
708 doit = false;
709 }
710 }
711 return doit;
712 }
713
newTag(const QString & newnameBase,QString & id)714 void KMyMoneyUtils::newTag(const QString& newnameBase, QString& id)
715 {
716 bool doit = true;
717
718 if (newnameBase != i18n("New Tag")) {
719 // Ask the user if that is what he intended to do?
720 const auto msg = i18n("<qt>Do you want to add <b>%1</b> as tag?</qt>", newnameBase);
721
722 if (KMessageBox::questionYesNo(nullptr, msg, i18n("New tag"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "NewTag") == KMessageBox::No) {
723 doit = false;
724 // we should not keep the 'no' setting because that can confuse people like
725 // I have seen in some usability tests. So we just delete it right away.
726 KSharedConfigPtr kconfig = KSharedConfig::openConfig();
727 if (kconfig) {
728 kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("NewTag"));
729 }
730 }
731 }
732
733 if (doit) {
734 MyMoneyFileTransaction ft;
735 try {
736 QString newname(newnameBase);
737 // adjust name until a unique name has been created
738 int count = 0;
739 for (;;) {
740 try {
741 MyMoneyFile::instance()->tagByName(newname);
742 newname = QString::fromLatin1("%1 [%2]").arg(newnameBase).arg(++count);
743 } catch (const MyMoneyException &) {
744 break;
745 }
746 }
747
748 MyMoneyTag ta;
749 ta.setName(newname);
750 MyMoneyFile::instance()->addTag(ta);
751 id = ta.id();
752 ft.commit();
753 } catch (const MyMoneyException &e) {
754 KMessageBox::detailedSorry(nullptr, i18n("Unable to add tag"), QString::fromLatin1(e.what()));
755 }
756 }
757 }
758
newInstitution(MyMoneyInstitution & institution)759 void KMyMoneyUtils::newInstitution(MyMoneyInstitution& institution)
760 {
761 auto file = MyMoneyFile::instance();
762
763 MyMoneyFileTransaction ft;
764
765 try {
766 file->addInstitution(institution);
767 ft.commit();
768
769 } catch (const MyMoneyException &e) {
770 KMessageBox::information(nullptr, i18n("Cannot add institution: %1", QString::fromLatin1(e.what())));
771 }
772 }
773
debug()774 QDebug KMyMoneyUtils::debug()
775 {
776 return qDebug() << QDateTime::currentDateTime().toString(QStringLiteral("HH:mm:ss.zzz"));
777 }
778
forecast()779 MyMoneyForecast KMyMoneyUtils::forecast()
780 {
781 MyMoneyForecast forecast;
782
783 // override object defaults with those of the application
784 forecast.setForecastCycles(KMyMoneySettings::forecastCycles());
785 forecast.setAccountsCycle(KMyMoneySettings::forecastAccountCycle());
786 forecast.setHistoryStartDate(QDate::currentDate().addDays(-forecast.forecastCycles()*forecast.accountsCycle()));
787 forecast.setHistoryEndDate(QDate::currentDate().addDays(-1));
788 forecast.setForecastDays(KMyMoneySettings::forecastDays());
789 forecast.setBeginForecastDay(KMyMoneySettings::beginForecastDay());
790 forecast.setForecastMethod(KMyMoneySettings::forecastMethod());
791 forecast.setHistoryMethod(KMyMoneySettings::historyMethod());
792 forecast.setIncludeFutureTransactions(KMyMoneySettings::includeFutureTransactions());
793 forecast.setIncludeScheduledTransactions(KMyMoneySettings::includeScheduledTransactions());
794
795 return forecast;
796 }
797
canUpdateAllAccounts()798 bool KMyMoneyUtils::canUpdateAllAccounts()
799 {
800 const auto file = MyMoneyFile::instance();
801 auto rc = false;
802 if (!file->storageAttached())
803 return rc;
804
805 QList<MyMoneyAccount> accList;
806 file->accountList(accList);
807 QList<MyMoneyAccount>::const_iterator it_a;
808 auto it_p = pPlugins.online.constEnd();
809 for (it_a = accList.constBegin(); (it_p == pPlugins.online.constEnd()) && (it_a != accList.constEnd()); ++it_a) {
810 if ((*it_a).hasOnlineMapping()) {
811 // check if provider is available
812 it_p = pPlugins.online.constFind((*it_a).onlineBankingSettings().value("provider").toLower());
813 if (it_p != pPlugins.online.constEnd()) {
814 QStringList protocols;
815 (*it_p)->protocols(protocols);
816 if (!protocols.isEmpty()) {
817 rc = true;
818 break;
819 }
820 }
821 }
822 }
823 return rc;
824 }
825
showStatementImportResult(const QStringList & resultMessages,uint statementCount)826 void KMyMoneyUtils::showStatementImportResult(const QStringList& resultMessages, uint statementCount)
827 {
828 KMessageBox::informationList(nullptr,
829 i18np("One statement has been processed with the following results:",
830 "%1 statements have been processed with the following results:",
831 statementCount),
832 !resultMessages.isEmpty() ?
833 resultMessages :
834 QStringList { i18np("No new transaction has been imported.", "No new transactions have been imported.", statementCount) },
835 i18n("Statement import statistics"));
836 }
837
normalizeNumericString(const qreal & val,const QLocale & loc,const char f,const int prec)838 QString KMyMoneyUtils::normalizeNumericString(const qreal& val, const QLocale& loc, const char f, const int prec)
839 {
840 return loc.toString(val, f, prec)
841 .remove(loc.groupSeparator())
842 .remove(QRegularExpression("0+$"))
843 .remove(QRegularExpression("\\" + loc.decimalPoint() + "$"));
844 }
845