File indexing completed on 2024-05-12 05:06:46

0001 /*
0002     SPDX-FileCopyrightText: 2002-2003 Michael Edwardes <mte@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2002-2016 Thomas Baumgart <tbaumgart@kde.org>
0004     SPDX-FileCopyrightText: 2002 Kevin Tambascio <ktambascio@users.sourceforge.net>
0005     SPDX-FileCopyrightText: 2017 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "mymoneyutils.h"
0010 
0011 // ----------------------------------------------------------------------------
0012 // QT Includes
0013 
0014 #include <QDate>
0015 #include <QProcess>
0016 #include <QRegularExpression>
0017 #include <QTimeZone>
0018 
0019 // ----------------------------------------------------------------------------
0020 // KDE Headers
0021 
0022 #include <KLocalizedString>
0023 
0024 // ----------------------------------------------------------------------------
0025 // Project Includes
0026 
0027 #include "journalmodel.h"
0028 #include "mymoneyaccount.h"
0029 #include "mymoneyexception.h"
0030 #include "mymoneyfile.h"
0031 #include "mymoneymoney.h"
0032 #include "mymoneyschedule.h"
0033 #include "mymoneysecurity.h"
0034 #include "mymoneysplit.h"
0035 #include "mymoneytransaction.h"
0036 
0037 QString MyMoneyUtils::getFileExtension(QString strFileName)
0038 {
0039     QString strTemp;
0040     if (!strFileName.isEmpty()) {
0041         //find last . deliminator
0042         int nLoc = strFileName.lastIndexOf('.');
0043         if (nLoc != -1) {
0044             strTemp = strFileName.right(strFileName.length() - (nLoc + 1));
0045             return strTemp.toUpper();
0046         }
0047     }
0048     return strTemp;
0049 }
0050 
0051 QString MyMoneyUtils::formatMoney(const MyMoneyMoney& val,
0052                                   const MyMoneyAccount& acc,
0053                                   const MyMoneySecurity& sec,
0054                                   bool showThousandSeparator)
0055 {
0056     return val.formatMoney(sec.tradingSymbol(),
0057                            val.denomToPrec(acc.fraction()),
0058                            showThousandSeparator);
0059 }
0060 
0061 QString MyMoneyUtils::formatMoney(const MyMoneyMoney& val,
0062                                   const MyMoneySecurity& sec,
0063                                   bool showThousandSeparator)
0064 {
0065     return val.formatMoney(sec.tradingSymbol(),
0066                            val.denomToPrec(sec.smallestAccountFraction()),
0067                            showThousandSeparator);
0068 }
0069 
0070 QString MyMoneyUtils::dateToString(const QDate& date)
0071 {
0072     if (!date.isNull() && date.isValid())
0073         return date.toString(Qt::ISODate);
0074 
0075     return QString();
0076 }
0077 
0078 QDate MyMoneyUtils::stringToDate(const QString& str)
0079 {
0080     if (!str.isEmpty()) {
0081         QDate date = QDate::fromString(str, Qt::ISODate);
0082         if (!date.isNull() && date.isValid())
0083             return date;
0084     }
0085     return {};
0086 }
0087 
0088 QString MyMoneyUtils::dateTimeToString(const QDateTime& dateTime)
0089 {
0090     return QDateTime(dateTime.date(), dateTime.time(), QTimeZone(dateTime.offsetFromUtc())).toString(Qt::ISODate);
0091 }
0092 
0093 QDateTime MyMoneyUtils::stringToDateTime(const QString& str)
0094 {
0095     if (!str.isEmpty()) {
0096         QDateTime dateTime = QDateTime::fromString(str, Qt::ISODate);
0097         if (!dateTime.isNull() && dateTime.isValid())
0098             return dateTime;
0099     }
0100     return {};
0101 }
0102 
0103 QString MyMoneyUtils::QStringEmpty(const QString& val)
0104 {
0105     if (!val.isEmpty())
0106         return QString(val);
0107 
0108     return QString();
0109 }
0110 
0111 unsigned long MyMoneyUtils::extractId(const QString& txt)
0112 {
0113     static const QRegularExpression digitRegex("\\d+");
0114     int pos;
0115     unsigned long rc = 0;
0116 
0117     pos = txt.indexOf(digitRegex);
0118     if (pos != -1) {
0119         rc = txt.midRef(pos).toInt();
0120     }
0121     return rc;
0122 }
0123 
0124 void MyMoneyUtils::dissectTransaction(const MyMoneyTransaction& transaction, const MyMoneySplit& split, MyMoneySplit& assetAccountSplit, QList<MyMoneySplit>& feeSplits, QList<MyMoneySplit>& interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency, eMyMoney::Split::InvestmentTransactionType& transactionType)
0125 {
0126     // collect the splits. split references the stock account and should already
0127     // be set up. assetAccountSplit references the corresponding asset account (maybe
0128     // empty), feeSplits is the list of all expenses and interestSplits
0129     // the list of all incomes
0130     assetAccountSplit = MyMoneySplit(); // set to none to check later if it was assigned
0131     auto file = MyMoneyFile::instance();
0132     const auto splits = transaction.splits();
0133     for (const auto& tsplit : splits) {
0134         auto acc = file->account(tsplit.accountId());
0135         if (tsplit.id() == split.id()) {
0136             security = file->security(acc.currencyId());
0137         } else if (acc.accountGroup() == eMyMoney::Account::Type::Expense) {
0138             feeSplits.append(tsplit);
0139             // feeAmount += tsplit.value();
0140         } else if (acc.accountGroup() == eMyMoney::Account::Type::Income) {
0141             interestSplits.append(tsplit);
0142             // interestAmount += tsplit.value();
0143         } else {
0144             if (assetAccountSplit == MyMoneySplit()) // first asset Account should be our requested brokerage account
0145                 assetAccountSplit = tsplit;
0146             else if (tsplit.value().isNegative())  // the rest (if present) is handled as fee or interest
0147                 feeSplits.append(tsplit);              // and shouldn't be allowed to override assetAccountSplit
0148             else if (tsplit.value().isPositive())
0149                 interestSplits.append(tsplit);
0150         }
0151     }
0152 
0153     // determine transaction type
0154     transactionType = split.investmentTransactionType();
0155     if (transactionType == eMyMoney::Split::InvestmentTransactionType::UnknownTransactionType) {
0156         transactionType = eMyMoney::Split::InvestmentTransactionType::BuyShares;
0157     }
0158 
0159     currency.setTradingSymbol("???");
0160     try {
0161         currency = file->security(transaction.commodity());
0162     } catch (const MyMoneyException &) {
0163     }
0164 }
0165 
0166 Q_GLOBAL_STATIC(QString, dateTimeFormat);
0167 Q_GLOBAL_STATIC(QString, timeFormat);
0168 Q_GLOBAL_STATIC(QString, dateFormat);
0169 
0170 QString MyMoneyUtils::formatDateTime(const QDateTime& dt)
0171 {
0172     if ((*dateTimeFormat).isEmpty()) {
0173         *dateTimeFormat = QLocale().dateTimeFormat(QLocale::ShortFormat);
0174         if (!(*dateTimeFormat).contains(QLatin1String("yyyy")) && (*dateTimeFormat).contains(QLatin1String("yy"))) {
0175             (*dateTimeFormat).replace(QLatin1String("yy"), QLatin1String("yyyy"));
0176         }
0177     }
0178     return dt.toString(*dateTimeFormat);
0179 }
0180 
0181 QString MyMoneyUtils::formatTime(const QTime& time)
0182 {
0183     if ((*timeFormat).isEmpty()) {
0184         *timeFormat = QLocale().timeFormat(QLocale::LongFormat);
0185     }
0186     return time.toString(*timeFormat);
0187 }
0188 
0189 QString MyMoneyUtils::formatDate(const QDate& date, QLocale::FormatType formatType)
0190 {
0191     if ((*dateFormat).isEmpty()) {
0192         *dateFormat = QLocale().dateFormat(formatType);
0193         if (!(*dateFormat).contains(QLatin1String("yyyy")) && (*dateFormat).contains(QLatin1String("yy"))) {
0194             (*dateFormat).replace(QLatin1String("yy"), QLatin1String("yyyy"));
0195         }
0196     }
0197     return date.toString(*dateFormat);
0198 }
0199 
0200 void MyMoneyUtils::clearFormatCaches()
0201 {
0202     (*dateTimeFormat).clear();
0203     (*timeFormat).clear();
0204     (*dateFormat).clear();
0205 }
0206 
0207 QString MyMoneyUtils::paymentMethodToString(eMyMoney::Schedule::PaymentType paymentType)
0208 {
0209     return i18n(MyMoneySchedule::paymentMethodToString(paymentType));
0210 }
0211 
0212 modifyTransactionWarnLevel_t MyMoneyUtils::transactionWarnLevel(const QStringList& journalEntryIds)
0213 {
0214     modifyTransactionWarnLevel_t level = NoWarning;
0215 
0216     const auto file = MyMoneyFile::instance();
0217     const auto journalModel = file->journalModel();
0218     const auto rows = journalModel->rowCount();
0219 
0220     QString lastTransactionId;
0221 
0222     for (auto row = 0; row < rows; ++row) {
0223         const auto idx = journalModel->index(row, 0);
0224         if (idx.data(eMyMoney::Model::JournalTransactionIdRole).toString() != lastTransactionId) {
0225             if (journalEntryIds.contains(idx.data(eMyMoney::Model::IdRole).toString())) {
0226                 modifyTransactionWarnLevel_t rc = NoWarning;
0227                 try {
0228                     const auto journalEntry = journalModel->itemByIndex(idx);
0229                     const auto splits = journalEntry.transaction().splits();
0230                     for (const auto& split : qAsConst(splits)) {
0231                         auto acc = file->account(split.accountId());
0232                         if (acc.isClosed())
0233                             rc = OneAccountClosed;
0234                         else if (split.reconcileFlag() == eMyMoney::Split::State::Frozen)
0235                             rc = OneSplitFrozen;
0236                         else if (split.reconcileFlag() == eMyMoney::Split::State::Reconciled && rc < OneSplitReconciled)
0237                             rc = OneSplitReconciled;
0238                     }
0239                 } catch (const MyMoneyException& e) {
0240                     qDebug() << "Exception in MyMoneyUtils::transactionWarnLevel():" << e.what();
0241                 }
0242                 lastTransactionId = idx.data(eMyMoney::Model::JournalTransactionIdRole).toString();
0243                 if (rc > level) {
0244                     level = rc;
0245                 }
0246             }
0247         }
0248     }
0249     return level;
0250 }
0251 
0252 modifyTransactionWarnLevel_t MyMoneyUtils::transactionWarnLevel(const QString& journalEntryId)
0253 {
0254     return transactionWarnLevel(QStringList(journalEntryId));
0255 }
0256 
0257 QString MyMoneyUtils::convertWildcardToRegularExpression(const QString& pattern)
0258 {
0259     QString rc;
0260     bool insideBrackets = false;
0261     int pos = 0;
0262     int len = pattern.length();
0263 
0264     // insert an escape character if c == d
0265     auto escapeChar = [&](const QChar& d, const QChar& c) {
0266         if (c == d) {
0267             rc.append(QLatin1Char('\\'));
0268         }
0269     };
0270 
0271     while (pos < len) {
0272         bool skipInResult(false);
0273         const auto c = pattern[pos];
0274         if (insideBrackets) {
0275             if (c == QLatin1Char(']')) {
0276                 insideBrackets = false;
0277             } else {
0278                 escapeChar(QLatin1Char('.'), c);
0279                 escapeChar(QLatin1Char('?'), c);
0280                 escapeChar(QLatin1Char('*'), c);
0281             }
0282         } else {
0283             if (c == QLatin1Char('[')) {
0284                 insideBrackets = true;
0285             } else if (c == QLatin1Char('?')) {
0286                 rc.append(QLatin1Char('.'));
0287                 skipInResult = true;
0288             } else if (c == QLatin1Char('*')) {
0289                 rc.append(QLatin1Char('.'));
0290             } else {
0291                 escapeChar(QLatin1Char('.'), c);
0292             }
0293         }
0294         if (!skipInResult) {
0295             rc.append(c);
0296         }
0297         ++pos;
0298     }
0299     return rc;
0300 }
0301 
0302 QString MyMoneyUtils::convertRegularExpressionToWildcard(const QString& pattern)
0303 {
0304     QString rc;
0305     int pos = 0;
0306     int len = pattern.length();
0307 
0308     while (pos < len) {
0309         auto c = pattern[pos];
0310         if (c == QLatin1Char('\\')) {
0311             if ((pos + 1) < len) {
0312                 c = pattern[++pos];
0313             }
0314         } else if (c == QLatin1Char('.')) {
0315             c = QLatin1Char('?');
0316             if ((pos + 1) < len) {
0317                 if (pattern[pos + 1] == QLatin1Char('*')) {
0318                     c = pattern[++pos];
0319                 }
0320             }
0321         }
0322         rc.append(c);
0323         ++pos;
0324     }
0325     return rc;
0326 }