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

0001 /*
0002     SPDX-FileCopyrightText: 2003-2019 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-FileCopyrightText: 2004 Ace Jones <acejones@users.sourceforge.net>
0004     SPDX-FileCopyrightText: 2008-2010 Alvaro Soliverez <asoliverez@gmail.com>
0005     SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "mymoneytransactionfilter.h"
0010 
0011 // ----------------------------------------------------------------------------
0012 // QT Includes
0013 
0014 #include <QDate>
0015 #include <QDebug>
0016 #include <QFlags>
0017 #include <QRegularExpression>
0018 
0019 // ----------------------------------------------------------------------------
0020 // KDE Includes
0021 
0022 // ----------------------------------------------------------------------------
0023 // Project Includes
0024 
0025 #include "mymoneymoney.h"
0026 #include "mymoneyfile.h"
0027 #include "mymoneyaccount.h"
0028 #include "mymoneysecurity.h"
0029 #include "mymoneypayee.h"
0030 #include "mymoneytag.h"
0031 #include "mymoneytransaction.h"
0032 #include "mymoneysplit.h"
0033 #include "mymoneyenums.h"
0034 
0035 class MyMoneyTransactionFilterPrivate {
0036 
0037 public:
0038     MyMoneyTransactionFilterPrivate()
0039         : m_reportAllSplits(false)
0040         , m_considerCategory(false)
0041         , m_considerCategorySplits(false)
0042         , m_matchOnly(false)
0043         , m_treatTransfersAsIncomeExpense(false)
0044         , m_matchingSplitsCount(0)
0045         , m_invertText(false)
0046         , m_filterIsRegExp(false)
0047     {
0048     }
0049 
0050     MyMoneyTransactionFilter::FilterSet m_filterSet;
0051     bool m_reportAllSplits;
0052     bool m_considerCategory;
0053     bool m_considerCategorySplits;
0054     bool m_matchOnly;
0055     bool m_treatTransfersAsIncomeExpense;
0056 
0057     uint m_matchingSplitsCount;
0058 
0059     QRegularExpression m_text;
0060     bool m_invertText;
0061     bool m_filterIsRegExp;
0062     QHash<QString, QString> m_accounts;
0063     QHash<QString, QString> m_payees;
0064     QHash<QString, QString> m_tags;
0065     QHash<QString, QString> m_categories;
0066     QHash<int, QString> m_states;
0067     QHash<int, QString> m_types;
0068     QHash<int, QString> m_validity;
0069     QString m_fromNr;
0070     QString m_toNr;
0071     QDate m_fromDate;
0072     QDate m_toDate;
0073     MyMoneyMoney m_fromAmount;
0074     MyMoneyMoney m_toAmount;
0075 };
0076 
0077 MyMoneyTransactionFilter::MyMoneyTransactionFilter() :
0078     d_ptr(new MyMoneyTransactionFilterPrivate)
0079 {
0080     Q_D(MyMoneyTransactionFilter);
0081     d->m_reportAllSplits = true;
0082     d->m_considerCategory = true;
0083 }
0084 
0085 MyMoneyTransactionFilter::MyMoneyTransactionFilter(const QString& id) :
0086     d_ptr(new MyMoneyTransactionFilterPrivate)
0087 {
0088     addAccount(id);
0089 }
0090 
0091 MyMoneyTransactionFilter::MyMoneyTransactionFilter(const MyMoneyTransactionFilter& other) :
0092     d_ptr(new MyMoneyTransactionFilterPrivate(*other.d_func()))
0093 {
0094 }
0095 
0096 MyMoneyTransactionFilter::~MyMoneyTransactionFilter()
0097 {
0098     Q_D(MyMoneyTransactionFilter);
0099     delete d;
0100 }
0101 
0102 void MyMoneyTransactionFilter::clear()
0103 {
0104     Q_D(MyMoneyTransactionFilter);
0105     d->m_filterSet = {};
0106     d->m_invertText = false;
0107     d->m_filterIsRegExp = false;
0108     d->m_accounts.clear();
0109     d->m_categories.clear();
0110     d->m_payees.clear();
0111     d->m_tags.clear();
0112     d->m_types.clear();
0113     d->m_states.clear();
0114     d->m_validity.clear();
0115     d->m_fromDate = QDate();
0116     d->m_toDate = QDate();
0117 }
0118 
0119 void MyMoneyTransactionFilter::clearAccountFilter()
0120 {
0121     Q_D(MyMoneyTransactionFilter);
0122     d->m_filterSet.setFlag(accountFilterActive);
0123     d->m_accounts.clear();
0124 }
0125 
0126 void MyMoneyTransactionFilter::setTextFilter(const QRegularExpression& text, bool isRegExp, bool invert)
0127 {
0128     Q_D(MyMoneyTransactionFilter);
0129     d->m_filterSet.setFlag(textFilterActive);
0130     d->m_filterIsRegExp = isRegExp;
0131     d->m_invertText = invert;
0132     d->m_text = text;
0133 }
0134 
0135 void MyMoneyTransactionFilter::addAccount(const QStringList& ids)
0136 {
0137     Q_D(MyMoneyTransactionFilter);
0138 
0139     d->m_filterSet.setFlag(accountFilterActive);
0140     for (const auto& id : ids)
0141         addAccount(id);
0142 }
0143 
0144 void MyMoneyTransactionFilter::addAccount(const QString& id)
0145 {
0146     Q_D(MyMoneyTransactionFilter);
0147     if (!d->m_accounts.isEmpty() && !id.isEmpty() &&
0148             d->m_accounts.contains(id))
0149         return;
0150 
0151     d->m_filterSet.setFlag(accountFilterActive);
0152     if (!id.isEmpty())
0153         d->m_accounts.insert(id, QString());
0154 }
0155 
0156 void MyMoneyTransactionFilter::addCategory(const QStringList& ids)
0157 {
0158     Q_D(MyMoneyTransactionFilter);
0159 
0160     d->m_filterSet.setFlag(categoryFilterActive);
0161     for (const auto& id : ids)
0162         addCategory(id);
0163 }
0164 
0165 void MyMoneyTransactionFilter::addCategory(const QString& id)
0166 {
0167     Q_D(MyMoneyTransactionFilter);
0168     if (!d->m_categories.isEmpty() && !id.isEmpty() &&
0169             d->m_categories.contains(id))
0170         return;
0171 
0172     d->m_filterSet.setFlag(categoryFilterActive);
0173     if (!id.isEmpty())
0174         d->m_categories.insert(id, QString());
0175 }
0176 
0177 void MyMoneyTransactionFilter::setDateFilter(const QDate& from, const QDate& to)
0178 {
0179     Q_D(MyMoneyTransactionFilter);
0180     d->m_filterSet.setFlag(dateFilterActive, from.isValid() || to.isValid());
0181     d->m_fromDate = from;
0182     d->m_toDate = to;
0183     if (from.isValid() && to.isValid()) {
0184         if (from > to) {
0185             d->m_fromDate = to;
0186             d->m_toDate = from;
0187         }
0188     }
0189 }
0190 
0191 void MyMoneyTransactionFilter::setAmountFilter(const MyMoneyMoney& from, const MyMoneyMoney& to)
0192 {
0193     Q_D(MyMoneyTransactionFilter);
0194     d->m_filterSet.setFlag(amountFilterActive);
0195     d->m_fromAmount = from.abs();
0196     d->m_toAmount = to.abs();
0197 
0198     // make sure that the user does not try to fool us  ;-)
0199     if (from > to)
0200         std::swap(d->m_fromAmount, d->m_toAmount);
0201 }
0202 
0203 void MyMoneyTransactionFilter::addPayee(const QString& id)
0204 {
0205     Q_D(MyMoneyTransactionFilter);
0206     if (!d->m_payees.isEmpty() && !id.isEmpty() &&
0207             d->m_payees.contains(id))
0208         return;
0209 
0210     d->m_filterSet.setFlag(payeeFilterActive);
0211     if (!id.isEmpty())
0212         d->m_payees.insert(id, QString());
0213 }
0214 
0215 void MyMoneyTransactionFilter::addTag(const QString& id)
0216 {
0217     Q_D(MyMoneyTransactionFilter);
0218     if (!d->m_tags.isEmpty() && !id.isEmpty() &&
0219             d->m_tags.contains(id))
0220         return;
0221 
0222     d->m_filterSet.setFlag(tagFilterActive);
0223     if (!id.isEmpty())
0224         d->m_tags.insert(id, QString());
0225 }
0226 
0227 void MyMoneyTransactionFilter::addType(const int type)
0228 {
0229     Q_D(MyMoneyTransactionFilter);
0230     if (!d->m_types.isEmpty() &&
0231             d->m_types.contains(type))
0232         return;
0233 
0234     d->m_filterSet.setFlag(typeFilterActive);
0235     d->m_types.insert(type, QString());
0236 }
0237 
0238 void MyMoneyTransactionFilter::addState(const int state)
0239 {
0240     Q_D(MyMoneyTransactionFilter);
0241     if (!d->m_states.isEmpty() &&
0242             d->m_states.contains(state))
0243         return;
0244 
0245     d->m_filterSet.setFlag(stateFilterActive);
0246     d->m_states.insert(state, QString());
0247 }
0248 
0249 void MyMoneyTransactionFilter::addValidity(const int type)
0250 {
0251     Q_D(MyMoneyTransactionFilter);
0252     if (!d->m_validity.isEmpty() &&
0253             d->m_validity.contains(type))
0254         return;
0255 
0256     d->m_filterSet.setFlag(validityFilterActive);
0257     d->m_validity.insert(type, QString());
0258 }
0259 
0260 void MyMoneyTransactionFilter::setNumberFilter(const QString& from, const QString& to)
0261 {
0262     Q_D(MyMoneyTransactionFilter);
0263     d->m_filterSet.setFlag(nrFilterActive);
0264     d->m_fromNr = from;
0265     d->m_toNr = to;
0266 }
0267 
0268 void MyMoneyTransactionFilter::setReportAllSplits(const bool report)
0269 {
0270     Q_D(MyMoneyTransactionFilter);
0271     d->m_reportAllSplits = report;
0272 }
0273 
0274 void MyMoneyTransactionFilter::setConsiderCategorySplits(const bool check)
0275 {
0276     Q_D(MyMoneyTransactionFilter);
0277     d->m_considerCategorySplits = check;
0278 }
0279 
0280 void MyMoneyTransactionFilter::setConsiderCategory(const bool check)
0281 {
0282     Q_D(MyMoneyTransactionFilter);
0283     d->m_considerCategory = check;
0284 }
0285 
0286 void MyMoneyTransactionFilter::setTreatTransfersAsIncomeExpense(const bool check)
0287 {
0288     Q_D(MyMoneyTransactionFilter);
0289     d->m_treatTransfersAsIncomeExpense = check;
0290 }
0291 
0292 bool MyMoneyTransactionFilter::treatTransfersAsIncomeExpense() const
0293 {
0294     Q_D(const MyMoneyTransactionFilter);
0295     return d->m_treatTransfersAsIncomeExpense;
0296 }
0297 
0298 uint MyMoneyTransactionFilter::matchingSplitsCount(const MyMoneyTransaction& transaction)
0299 {
0300     Q_D(MyMoneyTransactionFilter);
0301     d->m_matchOnly = true;
0302     matchingSplits(transaction);
0303     d->m_matchOnly = false;
0304     return d->m_matchingSplitsCount;
0305 }
0306 
0307 QVector<MyMoneySplit> MyMoneyTransactionFilter::matchingSplits(const MyMoneyTransaction& transaction)
0308 {
0309     Q_D(MyMoneyTransactionFilter);
0310 
0311     QVector<MyMoneySplit> matchingSplits;
0312     const auto file = MyMoneyFile::instance();
0313 
0314     // qDebug("T: %s", transaction.id().data());
0315     // if no filter is set, we can safely return a match
0316     // if we should report all splits, then we collect them
0317     if (!d->m_filterSet && d->m_reportAllSplits) {
0318         d->m_matchingSplitsCount = transaction.splitCount();
0319         if (!d->m_matchOnly)
0320             matchingSplits = QVector<MyMoneySplit>::fromList(transaction.splits());
0321         return matchingSplits;
0322     }
0323 
0324     d->m_matchingSplitsCount = 0;
0325     const auto filter = d->m_filterSet;
0326 
0327     // perform checks on the MyMoneyTransaction object first
0328 
0329     // check the date range
0330     if (filter & dateFilterActive) {
0331         if ((d->m_fromDate != QDate() &&
0332                 transaction.postDate() < d->m_fromDate) ||
0333                 (d->m_toDate != QDate() &&
0334                  transaction.postDate() > d->m_toDate)) {
0335             return matchingSplits;
0336         }
0337     }
0338 
0339     auto categoryMatched = !(filter.testFlag(categoryFilterActive));
0340     auto accountMatched = !(filter.testFlag(accountFilterActive));
0341     auto isTransfer = true;
0342 
0343     // check the transaction's validity
0344     if (filter & validityFilterActive) {
0345         if (!d->m_validity.isEmpty() &&
0346                 !d->m_validity.contains((int)validTransaction(transaction)))
0347             return matchingSplits;
0348     }
0349 
0350     // if d->m_reportAllSplits == false..
0351     // ...then we don't need splits...
0352     // ...but we need to know if there were any found
0353     auto isMatchingSplitsEmpty = true;
0354 
0355     auto extendedFilter = d->m_filterSet;
0356     extendedFilter.setFlag(dateFilterActive, false);
0357     extendedFilter.setFlag(accountFilterActive, false);
0358     extendedFilter.setFlag(categoryFilterActive, false);
0359 
0360     const auto needAccountMatch = filter.testFlag(accountFilterActive);
0361     const auto needCategoryMatch = filter.testFlag(categoryFilterActive);
0362     if (needAccountMatch || needCategoryMatch ||
0363             extendedFilter != 0) {
0364         const auto& splits = transaction.splits();
0365         for (const auto& s : splits) {
0366             if (needAccountMatch || needCategoryMatch) {
0367                 auto removeSplit = true;
0368                 if (d->m_considerCategory) {
0369                     switch (file->account(s.accountId()).accountGroup()) {
0370                     case eMyMoney::Account::Type::Income:
0371                     case eMyMoney::Account::Type::Expense:
0372                         isTransfer = false;
0373                         // check if the split references one of the categories in the list
0374                         if (needCategoryMatch) {
0375                             if (d->m_categories.isEmpty()) {
0376                                 // we're looking for transactions with 'no' categories
0377                                 d->m_matchingSplitsCount = 0;
0378                                 matchingSplits.clear();
0379                                 return matchingSplits;
0380                             } else if (d->m_categories.contains(s.accountId())) {
0381                                 categoryMatched = true;
0382                                 removeSplit = false;
0383                             }
0384                         }
0385                         break;
0386 
0387                     default:
0388                         // check if the split references one of the accounts in the list
0389                         if (!filter.testFlag(accountFilterActive)) {
0390                             removeSplit = false;
0391                         } else if (!d->m_accounts.isEmpty() &&
0392                                    d->m_accounts.contains(s.accountId())) {
0393                             accountMatched = true;
0394                             removeSplit = false;
0395                         }
0396 
0397                         break;
0398                     }
0399 
0400                 } else {
0401                     if (!filter.testFlag(accountFilterActive)) {
0402                         removeSplit = false;
0403                     } else if (!d->m_accounts.isEmpty() &&
0404                                d->m_accounts.contains(s.accountId())) {
0405                         accountMatched = true;
0406                         removeSplit = false;
0407                     }
0408                 }
0409 
0410                 if (removeSplit)
0411                     continue;
0412             }
0413 
0414             // check if less frequent filters are active
0415             if (extendedFilter != 0) {
0416                 const auto acc = file->account(s.accountId());
0417                 if (!(matchAmount(s) && matchText(s, acc)))
0418                     continue;
0419 
0420                 // Determine if this account is a category or an account
0421                 auto isCategory = false;
0422                 switch (acc.accountGroup()) {
0423                 case eMyMoney::Account::Type::Income:
0424                 case eMyMoney::Account::Type::Expense:
0425                     isCategory = true;
0426                 default:
0427                     break;
0428                 }
0429 
0430                 bool includeSplit = d->m_considerCategorySplits || (!d->m_considerCategorySplits && !isCategory);
0431                 if (includeSplit) {
0432                     // check the payee list
0433                     if (filter.testFlag(payeeFilterActive)) {
0434                         if (!d->m_payees.isEmpty()) {
0435                             if (s.payeeId().isEmpty() || !d->m_payees.contains(s.payeeId()))
0436                                 continue;
0437                         } else if (!s.payeeId().isEmpty())
0438                             continue;
0439                     }
0440 
0441                     // check the tag list
0442                     if (filter.testFlag(tagFilterActive)) {
0443                         const auto tags = s.tagIdList();
0444                         if (!d->m_tags.isEmpty()) {
0445                             if (tags.isEmpty()) {
0446                                 continue;
0447                             } else {
0448                                 auto found = false;
0449                                 for (const auto& tag : tags) {
0450                                     if (d->m_tags.contains(tag)) {
0451                                         found = true;
0452                                         break;
0453                                     }
0454                                 }
0455 
0456                                 if (!found)
0457                                     continue;
0458                             }
0459                         } else if (!tags.isEmpty())
0460                             continue;
0461                     }
0462 
0463                     // check the type list
0464                     if (filter.testFlag(typeFilterActive) &&
0465                             !d->m_types.isEmpty() &&
0466                             !d->m_types.contains(splitType(transaction, s, acc)))
0467                         continue;
0468 
0469                     // check the state list
0470                     if (filter.testFlag(stateFilterActive) &&
0471                             !d->m_states.isEmpty() &&
0472                             !d->m_states.contains(splitState(s)))
0473                         continue;
0474 
0475                     if (filter.testFlag(nrFilterActive) &&
0476                             ((!d->m_fromNr.isEmpty() && s.number() < d->m_fromNr) ||
0477                              (!d->m_toNr.isEmpty() && s.number() > d->m_toNr)))
0478                         continue;
0479 
0480                 } else if (filter & (payeeFilterActive | tagFilterActive | typeFilterActive | stateFilterActive | nrFilterActive)) {
0481                     continue;
0482                 }
0483             }
0484             if (d->m_reportAllSplits)
0485                 matchingSplits.append(s);
0486 
0487             isMatchingSplitsEmpty = false;
0488         }
0489 
0490         // check if we're looking for transactions without assigned category
0491         if (!categoryMatched && transaction.splitCount() == 1 && d->m_categories.isEmpty())
0492             categoryMatched = true;
0493 
0494         if ((needAccountMatch && !accountMatched) ||
0495                 (needCategoryMatch && !categoryMatched)) {
0496             matchingSplits.clear();
0497             return matchingSplits;
0498         }
0499     } else if (d->m_reportAllSplits) {
0500         const auto& splits = transaction.splits();
0501         for (const auto& s : splits)
0502             matchingSplits.append(s);
0503         d->m_matchingSplitsCount = matchingSplits.count();
0504         return matchingSplits;
0505     } else if (transaction.splitCount() > 0) {
0506         isMatchingSplitsEmpty = false;
0507     }
0508 
0509     // check if we're looking for transactions without assigned category
0510     if (!categoryMatched && transaction.splitCount() == 1 && d->m_categories.isEmpty())
0511         categoryMatched = true;
0512 
0513     // if there's no category filter and the category did not
0514     // match, then we still want to see this transaction if it's
0515     // a transfer
0516     if (!categoryMatched && !filter.testFlag(categoryFilterActive))
0517         categoryMatched = isTransfer;
0518 
0519     if (isMatchingSplitsEmpty || !(accountMatched && categoryMatched)) {
0520         d->m_matchingSplitsCount = 0;
0521         return matchingSplits;
0522     }
0523 
0524     if (!d->m_reportAllSplits && !isMatchingSplitsEmpty) {
0525         d->m_matchingSplitsCount = 1;
0526         if (!d->m_matchOnly)
0527             matchingSplits.append(transaction.firstSplit());
0528     } else {
0529         d->m_matchingSplitsCount = matchingSplits.count();
0530     }
0531 
0532     // all filters passed, I guess we have a match
0533     // qDebug("  C: %d", m_matchingSplits.count());
0534     return matchingSplits;
0535 }
0536 
0537 QDate MyMoneyTransactionFilter::fromDate() const
0538 {
0539     Q_D(const MyMoneyTransactionFilter);
0540     return d->m_fromDate;
0541 }
0542 
0543 QDate MyMoneyTransactionFilter::toDate() const
0544 {
0545     Q_D(const MyMoneyTransactionFilter);
0546     return d->m_toDate;
0547 }
0548 
0549 bool MyMoneyTransactionFilter::matchText(const MyMoneySplit& s, const MyMoneyAccount& acc) const
0550 {
0551     Q_D(const MyMoneyTransactionFilter);
0552     // check if the text is contained in one of the fields
0553     // memo, value, number, payee, tag, account
0554     if (d->m_filterSet & textFilterActive) {
0555         const auto file = MyMoneyFile::instance();
0556         const auto sec = file->security(acc.currencyId());
0557         if (s.memo().contains(d->m_text) ||
0558                 s.shares().formatMoney(acc.fraction(sec)).contains(d->m_text) ||
0559                 s.value().formatMoney(acc.fraction(sec)).contains(d->m_text) ||
0560                 s.number().contains(d->m_text) ||
0561                 (d->m_text.pattern().compare(s.transactionId())) == 0)
0562             return !d->m_invertText;
0563 
0564         if (acc.name().contains(d->m_text))
0565             return !d->m_invertText;
0566 
0567         if (!s.payeeId().isEmpty() && file->payee(s.payeeId()).name().contains(d->m_text))
0568             return !d->m_invertText;
0569 
0570         const auto& tagIdList = s.tagIdList();
0571         for (const auto& tag : tagIdList)
0572             if (file->tag(tag).name().contains(d->m_text))
0573                 return !d->m_invertText;
0574 
0575         return d->m_invertText;
0576     }
0577     return true;
0578 }
0579 
0580 bool MyMoneyTransactionFilter::matchAmount(const MyMoneySplit& s) const
0581 {
0582     Q_D(const MyMoneyTransactionFilter);
0583     if (d->m_filterSet & amountFilterActive) {
0584         const auto value  = s.value().abs();
0585         const auto shares = s.shares().abs();
0586         if ((value  < d->m_fromAmount || value  > d->m_toAmount) &&
0587                 (shares < d->m_fromAmount || shares > d->m_toAmount))
0588             return false;
0589     }
0590 
0591     return true;
0592 }
0593 
0594 bool MyMoneyTransactionFilter::match(const MyMoneySplit& s) const
0595 {
0596     const auto& acc = MyMoneyFile::instance()->account(s.accountId());
0597     return matchText(s, acc) && matchAmount(s);
0598 }
0599 
0600 bool MyMoneyTransactionFilter::match(const MyMoneyTransaction& transaction)
0601 {
0602     Q_D(MyMoneyTransactionFilter);
0603     d->m_matchOnly = true;
0604     matchingSplits(transaction);
0605     d->m_matchOnly = false;
0606     return d->m_matchingSplitsCount > 0;
0607 }
0608 
0609 int MyMoneyTransactionFilter::splitState(const MyMoneySplit& split) const
0610 {
0611     switch (split.reconcileFlag()) {
0612     default:
0613     case eMyMoney::Split::State::NotReconciled:
0614         return (int)eMyMoney::TransactionFilter::State::NotReconciled;
0615     case eMyMoney::Split::State::Cleared:
0616         return (int)eMyMoney::TransactionFilter::State::Cleared;
0617     case eMyMoney::Split::State::Reconciled:
0618         return (int)eMyMoney::TransactionFilter::State::Reconciled;
0619     case eMyMoney::Split::State::Frozen:
0620         return (int)eMyMoney::TransactionFilter::State::Frozen;
0621     }
0622 }
0623 
0624 int MyMoneyTransactionFilter::splitType(const MyMoneyTransaction& t, const MyMoneySplit& split, const MyMoneyAccount& acc) const
0625 {
0626     Q_D(const MyMoneyTransactionFilter);
0627     if (acc.isIncomeExpense())
0628         return (int)eMyMoney::TransactionFilter::Type::All;
0629 
0630     if (t.splitCount() == 2 && !d->m_treatTransfersAsIncomeExpense) {
0631         const auto& splits = t.splits();
0632         const auto file = MyMoneyFile::instance();
0633         const auto& a = splits.at(0).id().compare(split.id()) == 0 ? acc : file->account(splits.at(0).accountId());
0634         const auto& b = splits.at(1).id().compare(split.id()) == 0 ? acc : file->account(splits.at(1).accountId());
0635 
0636         if (!a.isIncomeExpense() && !b.isIncomeExpense())
0637             return (int)eMyMoney::TransactionFilter::Type::Transfers;
0638     }
0639 
0640     if (split.value().isPositive())
0641         return (int)eMyMoney::TransactionFilter::Type::Deposits;
0642 
0643     return (int)eMyMoney::TransactionFilter::Type::Payments;
0644 }
0645 
0646 eMyMoney::TransactionFilter::Validity MyMoneyTransactionFilter::validTransaction(const MyMoneyTransaction& t) const
0647 {
0648     MyMoneyMoney val;
0649     const auto& splits = t.splits();
0650     for (const auto& split : splits)
0651         val += split.value();
0652 
0653     return (val == MyMoneyMoney()) ? eMyMoney::TransactionFilter::Validity::Valid : eMyMoney::TransactionFilter::Validity::Invalid;
0654 }
0655 
0656 bool MyMoneyTransactionFilter::includesCategory(const QString& cat) const
0657 {
0658     Q_D(const MyMoneyTransactionFilter);
0659     return !d->m_filterSet.testFlag(categoryFilterActive) || d->m_categories.contains(cat);
0660 }
0661 
0662 bool MyMoneyTransactionFilter::includesAccount(const QString& acc) const
0663 {
0664     Q_D(const MyMoneyTransactionFilter);
0665     return !d->m_filterSet.testFlag(accountFilterActive) || d->m_accounts.contains(acc);
0666 }
0667 
0668 bool MyMoneyTransactionFilter::includesPayee(const QString& pye) const
0669 {
0670     Q_D(const MyMoneyTransactionFilter);
0671     return !d->m_filterSet.testFlag(payeeFilterActive) || d->m_payees.contains(pye);
0672 }
0673 
0674 bool MyMoneyTransactionFilter::includesTag(const QString& tag) const
0675 {
0676     Q_D(const MyMoneyTransactionFilter);
0677     return !d->m_filterSet.testFlag(tagFilterActive) || d->m_tags.contains(tag);
0678 }
0679 
0680 bool MyMoneyTransactionFilter::dateFilter(QDate& from, QDate& to) const
0681 {
0682     Q_D(const MyMoneyTransactionFilter);
0683     from = d->m_fromDate;
0684     to = d->m_toDate;
0685     return d->m_filterSet.testFlag(dateFilterActive);
0686 }
0687 
0688 bool MyMoneyTransactionFilter::amountFilter(MyMoneyMoney& from, MyMoneyMoney& to) const
0689 {
0690     Q_D(const MyMoneyTransactionFilter);
0691     from = d->m_fromAmount;
0692     to = d->m_toAmount;
0693     return d->m_filterSet.testFlag(amountFilterActive);
0694 }
0695 
0696 bool MyMoneyTransactionFilter::numberFilter(QString& from, QString& to) const
0697 {
0698     Q_D(const MyMoneyTransactionFilter);
0699     from = d->m_fromNr;
0700     to = d->m_toNr;
0701     return d->m_filterSet.testFlag(nrFilterActive);
0702 }
0703 
0704 bool MyMoneyTransactionFilter::payees(QStringList& list) const
0705 {
0706     Q_D(const MyMoneyTransactionFilter);
0707     auto result = d->m_filterSet.testFlag(payeeFilterActive);
0708 
0709     if (result) {
0710         QHashIterator<QString, QString> it_payee(d->m_payees);
0711         while (it_payee.hasNext()) {
0712             it_payee.next();
0713             list += it_payee.key();
0714         }
0715     }
0716     return result;
0717 }
0718 
0719 QStringList MyMoneyTransactionFilter::payees() const
0720 {
0721     Q_D(const MyMoneyTransactionFilter);
0722     QStringList list;
0723 
0724     if (d->m_filterSet.testFlag(payeeFilterActive)) {
0725         QHashIterator<QString, QString> it_payee(d->m_payees);
0726         while (it_payee.hasNext()) {
0727             it_payee.next();
0728             list += it_payee.key();
0729         }
0730     }
0731     return list;
0732 }
0733 
0734 bool MyMoneyTransactionFilter::tags(QStringList& list) const
0735 {
0736     Q_D(const MyMoneyTransactionFilter);
0737     auto result = d->m_filterSet.testFlag(tagFilterActive);
0738 
0739     if (result) {
0740         QHashIterator<QString, QString> it_tag(d->m_tags);
0741         while (it_tag.hasNext()) {
0742             it_tag.next();
0743             list += it_tag.key();
0744         }
0745     }
0746     return result;
0747 }
0748 
0749 QStringList MyMoneyTransactionFilter::tags() const
0750 {
0751     QStringList tagIds;
0752     tags(tagIds);
0753     return tagIds;
0754 }
0755 
0756 bool MyMoneyTransactionFilter::accounts(QStringList& list) const
0757 {
0758     Q_D(const MyMoneyTransactionFilter);
0759     auto result = d->m_filterSet.testFlag(accountFilterActive);
0760 
0761     if (result) {
0762         QHashIterator<QString, QString> it_account(d->m_accounts);
0763         while (it_account.hasNext()) {
0764             it_account.next();
0765             QString account = it_account.key();
0766             list += account;
0767         }
0768     }
0769     return result;
0770 }
0771 
0772 QStringList MyMoneyTransactionFilter::accounts() const
0773 {
0774     Q_D(const MyMoneyTransactionFilter);
0775     QStringList list;
0776 
0777     if (d->m_filterSet.testFlag(accountFilterActive)) {
0778         QHashIterator<QString, QString> it_account(d->m_accounts);
0779         while (it_account.hasNext()) {
0780             it_account.next();
0781             QString account = it_account.key();
0782             list += account;
0783         }
0784     }
0785     return list;
0786 }
0787 
0788 bool MyMoneyTransactionFilter::categories(QStringList& list) const
0789 {
0790     Q_D(const MyMoneyTransactionFilter);
0791     auto result = d->m_filterSet.testFlag(categoryFilterActive);
0792 
0793     if (result) {
0794         QHashIterator<QString, QString> it_category(d->m_categories);
0795         while (it_category.hasNext()) {
0796             it_category.next();
0797             list += it_category.key();
0798         }
0799     }
0800     return result;
0801 }
0802 
0803 bool MyMoneyTransactionFilter::types(QList<int>& list) const
0804 {
0805     Q_D(const MyMoneyTransactionFilter);
0806     auto result = d->m_filterSet.testFlag(typeFilterActive);
0807 
0808     if (result) {
0809         QHashIterator<int, QString> it_type(d->m_types);
0810         while (it_type.hasNext()) {
0811             it_type.next();
0812             list += it_type.key();
0813         }
0814     }
0815     return result;
0816 }
0817 
0818 bool MyMoneyTransactionFilter::states(QList<int>& list) const
0819 {
0820     Q_D(const MyMoneyTransactionFilter);
0821     auto result = d->m_filterSet.testFlag(stateFilterActive);
0822 
0823     if (result) {
0824         QHashIterator<int, QString> it_state(d->m_states);
0825         while (it_state.hasNext()) {
0826             it_state.next();
0827             list += it_state.key();
0828         }
0829     }
0830     return result;
0831 }
0832 
0833 bool MyMoneyTransactionFilter::validities(QList<int>& list) const
0834 {
0835     Q_D(const MyMoneyTransactionFilter);
0836     auto result = d->m_filterSet.testFlag(validityFilterActive);
0837 
0838     if (result) {
0839         QHashIterator<int, QString> it_validity(d->m_validity);
0840         while (it_validity.hasNext()) {
0841             it_validity.next();
0842             list += it_validity.key();
0843         }
0844     }
0845     return result;
0846 }
0847 
0848 bool MyMoneyTransactionFilter::firstType(int&i) const
0849 {
0850     Q_D(const MyMoneyTransactionFilter);
0851     auto result = d->m_filterSet.testFlag(typeFilterActive);
0852 
0853     if (result) {
0854         QHashIterator<int, QString> it_type(d->m_types);
0855         if (it_type.hasNext()) {
0856             it_type.next();
0857             i = it_type.key();
0858         }
0859     }
0860     return result;
0861 }
0862 
0863 bool MyMoneyTransactionFilter::firstState(int&i) const
0864 {
0865     Q_D(const MyMoneyTransactionFilter);
0866     auto result = d->m_filterSet.testFlag(stateFilterActive);
0867 
0868     if (result) {
0869         QHashIterator<int, QString> it_state(d->m_states);
0870         if (it_state.hasNext()) {
0871             it_state.next();
0872             i = it_state.key();
0873         }
0874     }
0875     return result;
0876 }
0877 
0878 bool MyMoneyTransactionFilter::firstValidity(int&i) const
0879 {
0880     Q_D(const MyMoneyTransactionFilter);
0881     auto result = d->m_filterSet.testFlag(validityFilterActive);
0882 
0883     if (result) {
0884         QHashIterator<int, QString> it_validity(d->m_validity);
0885         if (it_validity.hasNext()) {
0886             it_validity.next();
0887             i = it_validity.key();
0888         }
0889     }
0890     return result;
0891 }
0892 
0893 bool MyMoneyTransactionFilter::textFilter(QRegularExpression& text, bool& isRegExp) const
0894 {
0895     Q_D(const MyMoneyTransactionFilter);
0896     text = d->m_text;
0897     isRegExp = d->m_filterIsRegExp;
0898     return d->m_filterSet.testFlag(textFilterActive);
0899 }
0900 
0901 bool MyMoneyTransactionFilter::isInvertingText() const
0902 {
0903     Q_D(const MyMoneyTransactionFilter);
0904     return d->m_invertText;
0905 }
0906 
0907 void MyMoneyTransactionFilter::setDateFilter(eMyMoney::TransactionFilter::Date range)
0908 {
0909     QDate from, to;
0910     if (translateDateRange(range, from, to))
0911         setDateFilter(from, to);
0912 }
0913 
0914 static int fiscalYearStartMonth = 1;
0915 static int fiscalYearStartDay = 1;
0916 
0917 void MyMoneyTransactionFilter::setFiscalYearStart(int firstMonth, int firstDay)
0918 {
0919     fiscalYearStartMonth = firstMonth;
0920     fiscalYearStartDay = firstDay;
0921 }
0922 
0923 bool MyMoneyTransactionFilter::translateDateRange(eMyMoney::TransactionFilter::Date id, QDate& start, QDate& end)
0924 {
0925     bool rc = true;
0926     int yr = QDate::currentDate().year();
0927     int mon = QDate::currentDate().month();
0928 
0929     switch (id) {
0930     case eMyMoney::TransactionFilter::Date::All:
0931         start = QDate();
0932         end = QDate();
0933         break;
0934     case eMyMoney::TransactionFilter::Date::AsOfToday:
0935         start = QDate();
0936         end =  QDate::currentDate();
0937         break;
0938     case eMyMoney::TransactionFilter::Date::CurrentMonth:
0939         start = QDate(yr, mon, 1);
0940         end = QDate(yr, mon, 1).addMonths(1).addDays(-1);
0941         break;
0942     case eMyMoney::TransactionFilter::Date::CurrentYear:
0943         start = QDate(yr, 1, 1);
0944         end = QDate(yr, 12, 31);
0945         break;
0946     case eMyMoney::TransactionFilter::Date::MonthToDate:
0947         start = QDate(yr, mon, 1);
0948         end = QDate::currentDate();
0949         break;
0950     case eMyMoney::TransactionFilter::Date::YearToDate:
0951         start = QDate(yr, 1, 1);
0952         end = QDate::currentDate();
0953         break;
0954     case eMyMoney::TransactionFilter::Date::YearToMonth:
0955         start = QDate(yr, 1, 1);
0956         end = QDate(yr, mon, 1).addDays(-1);
0957         break;
0958     case eMyMoney::TransactionFilter::Date::LastMonth:
0959         start = QDate(yr, mon, 1).addMonths(-1);
0960         end = QDate(yr, mon, 1).addDays(-1);
0961         break;
0962     case eMyMoney::TransactionFilter::Date::LastYear:
0963         start = QDate(yr, 1, 1).addYears(-1);
0964         end = QDate(yr, 12, 31).addYears(-1);
0965         break;
0966     case eMyMoney::TransactionFilter::Date::Last7Days:
0967         start = QDate::currentDate().addDays(-7);
0968         end = QDate::currentDate();
0969         break;
0970     case eMyMoney::TransactionFilter::Date::Last30Days:
0971         start = QDate::currentDate().addDays(-30);
0972         end = QDate::currentDate();
0973         break;
0974     case eMyMoney::TransactionFilter::Date::Last3Months:
0975         start = QDate::currentDate().addMonths(-3);
0976         end = QDate::currentDate();
0977         break;
0978     case eMyMoney::TransactionFilter::Date::Last6Months:
0979         start = QDate::currentDate().addMonths(-6);
0980         end = QDate::currentDate();
0981         break;
0982     case eMyMoney::TransactionFilter::Date::Last11Months:
0983         start = QDate(yr, mon, 1).addMonths(-12);
0984         end = QDate(yr, mon, 1).addDays(-1);
0985         break;
0986     case eMyMoney::TransactionFilter::Date::Last12Months:
0987         start = QDate::currentDate().addMonths(-12);
0988         end = QDate::currentDate();
0989         break;
0990     case eMyMoney::TransactionFilter::Date::Next7Days:
0991         start = QDate::currentDate();
0992         end = QDate::currentDate().addDays(7);
0993         break;
0994     case eMyMoney::TransactionFilter::Date::Next30Days:
0995         start = QDate::currentDate();
0996         end = QDate::currentDate().addDays(30);
0997         break;
0998     case eMyMoney::TransactionFilter::Date::Next3Months:
0999         start = QDate::currentDate();
1000         end = QDate::currentDate().addMonths(3);
1001         break;
1002     case eMyMoney::TransactionFilter::Date::Next6Months:
1003         start = QDate::currentDate();
1004         end = QDate::currentDate().addMonths(6);
1005         break;
1006     case eMyMoney::TransactionFilter::Date::Next12Months:
1007         start = QDate::currentDate();
1008         end = QDate::currentDate().addMonths(12);
1009         break;
1010     case eMyMoney::TransactionFilter::Date::Next18Months:
1011         start = QDate::currentDate();
1012         end = QDate::currentDate().addMonths(18);
1013         break;
1014     case eMyMoney::TransactionFilter::Date::UserDefined:
1015         start = QDate();
1016         end = QDate();
1017         break;
1018     case eMyMoney::TransactionFilter::Date::Last3ToNext3Months:
1019         start = QDate::currentDate().addMonths(-3);
1020         end = QDate::currentDate().addMonths(3);
1021         break;
1022     case eMyMoney::TransactionFilter::Date::CurrentQuarter:
1023         start = QDate(yr, mon - ((mon - 1) % 3), 1);
1024         end = start.addMonths(3).addDays(-1);
1025         break;
1026     case eMyMoney::TransactionFilter::Date::LastQuarter:
1027         start = QDate(yr, mon - ((mon - 1) % 3), 1).addMonths(-3);
1028         end = start.addMonths(3).addDays(-1);
1029         break;
1030     case eMyMoney::TransactionFilter::Date::NextQuarter:
1031         start = QDate(yr, mon - ((mon - 1) % 3), 1).addMonths(3);
1032         end = start.addMonths(3).addDays(-1);
1033         break;
1034     case eMyMoney::TransactionFilter::Date::CurrentFiscalYear:
1035         start = QDate(QDate::currentDate().year(), fiscalYearStartMonth, fiscalYearStartDay);
1036         if (QDate::currentDate() < start)
1037             start = start.addYears(-1);
1038         end = start.addYears(1).addDays(-1);
1039         break;
1040     case eMyMoney::TransactionFilter::Date::LastFiscalYear:
1041         start = QDate(QDate::currentDate().year(), fiscalYearStartMonth, fiscalYearStartDay);
1042         if (QDate::currentDate() < start)
1043             start = start.addYears(-1);
1044         start = start.addYears(-1);
1045         end = start.addYears(1).addDays(-1);
1046         break;
1047     case eMyMoney::TransactionFilter::Date::Today:
1048         start = QDate::currentDate();
1049         end =  QDate::currentDate();
1050         break;
1051     default:
1052         qWarning("Unknown date identifier %d in MyMoneyTransactionFilter::translateDateRange()", (int)id);
1053         rc = false;
1054         break;
1055     }
1056     return rc;
1057 }
1058 
1059 MyMoneyTransactionFilter::FilterSet MyMoneyTransactionFilter::filterSet() const
1060 {
1061     Q_D(const MyMoneyTransactionFilter);
1062     return d->m_filterSet;
1063 }
1064 
1065 void MyMoneyTransactionFilter::removeReference(const QString& id)
1066 {
1067     Q_D(MyMoneyTransactionFilter);
1068     if (d->m_accounts.end() != d->m_accounts.find(id)) {
1069         qDebug("%s", qPrintable(QString("Remove account '%1' from report").arg(id)));
1070         d->m_accounts.take(id);
1071     } else if (d->m_categories.end() != d->m_categories.find(id)) {
1072         qDebug("%s", qPrintable(QString("Remove category '%1' from report").arg(id)));
1073         d->m_categories.remove(id);
1074     } else if (d->m_payees.end() != d->m_payees.find(id)) {
1075         qDebug("%s", qPrintable(QString("Remove payee '%1' from report").arg(id)));
1076         d->m_payees.remove(id);
1077     } else if (d->m_tags.end() != d->m_tags.find(id)) {
1078         qDebug("%s", qPrintable(QString("Remove tag '%1' from report").arg(id)));
1079         d->m_tags.remove(id);
1080     }
1081 }