diff --git a/kmymoney/mymoney/mymoneytransactionfilter.cpp b/kmymoney/mymoney/mymoneytransactionfilter.cpp index 11e4e01e5..279a30377 100644 --- a/kmymoney/mymoney/mymoneytransactionfilter.cpp +++ b/kmymoney/mymoney/mymoneytransactionfilter.cpp @@ -1,982 +1,982 @@ /* * Copyright 2003-2018 Thomas Baumgart * Copyright 2004 Ace Jones * Copyright 2008-2010 Alvaro Soliverez * Copyright 2017-2018 Łukasz Wojniłowicz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mymoneytransactionfilter.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneymoney.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneypayee.h" #include "mymoneytag.h" #include "mymoneytransaction.h" #include "mymoneysplit.h" #include "mymoneyenums.h" class MyMoneyTransactionFilterPrivate { public: MyMoneyTransactionFilterPrivate() : m_reportAllSplits(false) , m_considerCategory(false) , m_matchOnly(false) , m_matchingSplitsCount(0) , m_invertText(false) { m_filterSet.allFilter = 0; } MyMoneyTransactionFilter::FilterSet m_filterSet; bool m_reportAllSplits; bool m_considerCategory; bool m_matchOnly; uint m_matchingSplitsCount; QRegExp m_text; bool m_invertText; QHash m_accounts; QHash m_payees; QHash m_tags; QHash m_categories; QHash m_states; QHash m_types; QHash m_validity; QString m_fromNr, m_toNr; QDate m_fromDate, m_toDate; MyMoneyMoney m_fromAmount, m_toAmount; }; MyMoneyTransactionFilter::MyMoneyTransactionFilter() : d_ptr(new MyMoneyTransactionFilterPrivate) { Q_D(MyMoneyTransactionFilter); d->m_reportAllSplits = true; d->m_considerCategory = true; } MyMoneyTransactionFilter::MyMoneyTransactionFilter(const QString& id) : d_ptr(new MyMoneyTransactionFilterPrivate) { addAccount(id); } MyMoneyTransactionFilter::MyMoneyTransactionFilter(const MyMoneyTransactionFilter& other) : d_ptr(new MyMoneyTransactionFilterPrivate(*other.d_func())) { } MyMoneyTransactionFilter::~MyMoneyTransactionFilter() { Q_D(MyMoneyTransactionFilter); delete d; } void MyMoneyTransactionFilter::clear() { Q_D(MyMoneyTransactionFilter); d->m_filterSet.allFilter = 0; d->m_invertText = false; d->m_accounts.clear(); d->m_categories.clear(); d->m_payees.clear(); d->m_tags.clear(); d->m_types.clear(); d->m_states.clear(); d->m_validity.clear(); d->m_fromDate = QDate(); d->m_toDate = QDate(); } void MyMoneyTransactionFilter::clearAccountFilter() { Q_D(MyMoneyTransactionFilter); d->m_filterSet.singleFilter.accountFilter = 0; d->m_accounts.clear(); } void MyMoneyTransactionFilter::setTextFilter(const QRegExp& text, bool invert) { Q_D(MyMoneyTransactionFilter); d->m_filterSet.singleFilter.textFilter = 1; d->m_invertText = invert; d->m_text = text; } void MyMoneyTransactionFilter::addAccount(const QStringList& ids) { Q_D(MyMoneyTransactionFilter); d->m_filterSet.singleFilter.accountFilter = 1; for (const auto& id : ids) addAccount(id); } void MyMoneyTransactionFilter::addAccount(const QString& id) { Q_D(MyMoneyTransactionFilter); if (!d->m_accounts.isEmpty() && !id.isEmpty() && d->m_accounts.contains(id)) return; d->m_filterSet.singleFilter.accountFilter = 1; if (!id.isEmpty()) d->m_accounts.insert(id, QString()); } void MyMoneyTransactionFilter::addCategory(const QStringList& ids) { Q_D(MyMoneyTransactionFilter); d->m_filterSet.singleFilter.categoryFilter = 1; for (const auto& id : ids) addCategory(id); } void MyMoneyTransactionFilter::addCategory(const QString& id) { Q_D(MyMoneyTransactionFilter); if (!d->m_categories.isEmpty() && !id.isEmpty() && d->m_categories.contains(id)) return; d->m_filterSet.singleFilter.categoryFilter = 1; if (!id.isEmpty()) d->m_categories.insert(id, QString()); } void MyMoneyTransactionFilter::setDateFilter(const QDate& from, const QDate& to) { Q_D(MyMoneyTransactionFilter); d->m_filterSet.singleFilter.dateFilter = from.isValid() | to.isValid(); d->m_fromDate = from; d->m_toDate = to; } void MyMoneyTransactionFilter::setAmountFilter(const MyMoneyMoney& from, const MyMoneyMoney& to) { Q_D(MyMoneyTransactionFilter); d->m_filterSet.singleFilter.amountFilter = 1; d->m_fromAmount = from.abs(); d->m_toAmount = to.abs(); // make sure that the user does not try to fool us ;-) if (from > to) std::swap(d->m_fromAmount, d->m_toAmount); } void MyMoneyTransactionFilter::addPayee(const QString& id) { Q_D(MyMoneyTransactionFilter); if (!d->m_payees.isEmpty() && !id.isEmpty() && d->m_payees.contains(id)) return; d->m_filterSet.singleFilter.payeeFilter = 1; if (!id.isEmpty()) d->m_payees.insert(id, QString()); } void MyMoneyTransactionFilter::addTag(const QString& id) { Q_D(MyMoneyTransactionFilter); if (!d->m_tags.isEmpty() && !id.isEmpty() && d->m_tags.contains(id)) return; d->m_filterSet.singleFilter.tagFilter = 1; if (!id.isEmpty()) d->m_tags.insert(id, QString()); } void MyMoneyTransactionFilter::addType(const int type) { Q_D(MyMoneyTransactionFilter); if (!d->m_types.isEmpty() && d->m_types.contains(type)) return; d->m_filterSet.singleFilter.typeFilter = 1; d->m_types.insert(type, QString()); } void MyMoneyTransactionFilter::addState(const int state) { Q_D(MyMoneyTransactionFilter); if (!d->m_states.isEmpty() && d->m_states.contains(state)) return; d->m_filterSet.singleFilter.stateFilter = 1; d->m_states.insert(state, QString()); } void MyMoneyTransactionFilter::addValidity(const int type) { Q_D(MyMoneyTransactionFilter); if (!d->m_validity.isEmpty() && d->m_validity.contains(type)) return; d->m_filterSet.singleFilter.validityFilter = 1; d->m_validity.insert(type, QString()); } void MyMoneyTransactionFilter::setNumberFilter(const QString& from, const QString& to) { Q_D(MyMoneyTransactionFilter); d->m_filterSet.singleFilter.nrFilter = 1; d->m_fromNr = from; d->m_toNr = to; } void MyMoneyTransactionFilter::setReportAllSplits(const bool report) { Q_D(MyMoneyTransactionFilter); d->m_reportAllSplits = report; } void MyMoneyTransactionFilter::setConsiderCategory(const bool check) { Q_D(MyMoneyTransactionFilter); d->m_considerCategory = check; } uint MyMoneyTransactionFilter::matchingSplitsCount(const MyMoneyTransaction& transaction) { Q_D(MyMoneyTransactionFilter); d->m_matchOnly = true; matchingSplits(transaction); d->m_matchOnly = false; return d->m_matchingSplitsCount; } QVector MyMoneyTransactionFilter::matchingSplits(const MyMoneyTransaction& transaction) { Q_D(MyMoneyTransactionFilter); QVector matchingSplits; const auto file = MyMoneyFile::instance(); // qDebug("T: %s", transaction.id().data()); // if no filter is set, we can safely return a match // if we should report all splits, then we collect them if (!d->m_filterSet.allFilter && d->m_reportAllSplits) { d->m_matchingSplitsCount = transaction.splitCount(); if (!d->m_matchOnly) matchingSplits = QVector::fromList(transaction.splits()); return matchingSplits; } d->m_matchingSplitsCount = 0; const auto filter = d->m_filterSet.singleFilter; // perform checks on the MyMoneyTransaction object first // check the date range if (filter.dateFilter) { if ((d->m_fromDate != QDate() && transaction.postDate() < d->m_fromDate) || (d->m_toDate != QDate() && transaction.postDate() > d->m_toDate)) { return matchingSplits; } } auto categoryMatched = !filter.categoryFilter; auto accountMatched = !filter.accountFilter; auto isTransfer = true; // check the transaction's validity if (filter.validityFilter) { if (!d->m_validity.isEmpty() && !d->m_validity.contains((int)validTransaction(transaction))) return matchingSplits; } // if d->m_reportAllSplits == false.. // ...then we don't need splits... // ...but we need to know if there were any found auto isMatchingSplitsEmpty = true; auto extendedFilter = d->m_filterSet; extendedFilter.singleFilter.dateFilter = extendedFilter.singleFilter.accountFilter = extendedFilter.singleFilter.categoryFilter = 0; if (filter.accountFilter || filter.categoryFilter || extendedFilter.allFilter) { const auto& splits = transaction.splits(); for (const auto& s : splits) { if (filter.accountFilter || filter.categoryFilter) { auto removeSplit = true; if (d->m_considerCategory) { switch (file->account(s.accountId()).accountGroup()) { case eMyMoney::Account::Type::Income: case eMyMoney::Account::Type::Expense: isTransfer = false; // check if the split references one of the categories in the list if (filter.categoryFilter) { if (d->m_categories.isEmpty()) { // we're looking for transactions with 'no' categories d->m_matchingSplitsCount = 0; matchingSplits.clear(); return matchingSplits; } else if (d->m_categories.contains(s.accountId())) { categoryMatched = true; removeSplit = false; } } break; default: // check if the split references one of the accounts in the list if (!filter.accountFilter) { removeSplit = false; } else if (!d->m_accounts.isEmpty() && d->m_accounts.contains(s.accountId())) { accountMatched = true; removeSplit = false; } break; } } else { if (!filter.accountFilter) { removeSplit = false; } else if (!d->m_accounts.isEmpty() && d->m_accounts.contains(s.accountId())) { accountMatched = true; removeSplit = false; } } if (removeSplit) continue; } // check if less frequent filters are active if (extendedFilter.allFilter) { const auto acc = file->account(s.accountId()); if (!(matchAmount(s) && matchText(s, acc))) continue; // Determine if this account is a category or an account auto isCategory = false; switch (acc.accountGroup()) { case eMyMoney::Account::Type::Income: case eMyMoney::Account::Type::Expense: isCategory = true; default: break; } if (!isCategory) { // check the payee list if (filter.payeeFilter) { if (!d->m_payees.isEmpty()) { if (s.payeeId().isEmpty() || !d->m_payees.contains(s.payeeId())) continue; } else if (!s.payeeId().isEmpty()) continue; } // check the tag list if (filter.tagFilter) { const auto tags = s.tagIdList(); if (!d->m_tags.isEmpty()) { if (tags.isEmpty()) { continue; } else { auto found = false; for (const auto& tag : tags) { if (d->m_tags.contains(tag)) { found = true; break; } } if (!found) continue; } } else if (!tags.isEmpty()) continue; } // check the type list if (filter.typeFilter && !d->m_types.isEmpty() && !d->m_types.contains(splitType(transaction, s, acc))) continue; // check the state list if (filter.stateFilter && !d->m_states.isEmpty() && !d->m_states.contains(splitState(s))) continue; if (filter.nrFilter && ((!d->m_fromNr.isEmpty() && s.number() < d->m_fromNr) || (!d->m_toNr.isEmpty() && s.number() > d->m_toNr))) continue; } else if (filter.payeeFilter || filter.tagFilter || filter.typeFilter || filter.stateFilter || filter.nrFilter) { continue; } } if (d->m_reportAllSplits) matchingSplits.append(s); isMatchingSplitsEmpty = false; } } else if (d->m_reportAllSplits) { const auto& splits = transaction.splits(); for (const auto& s : splits) matchingSplits.append(s); d->m_matchingSplitsCount = matchingSplits.count(); return matchingSplits; } else if (transaction.splitCount() > 0) { isMatchingSplitsEmpty = false; } // check if we're looking for transactions without assigned category if (!categoryMatched && transaction.splitCount() == 1 && d->m_categories.isEmpty()) categoryMatched = true; // if there's no category filter and the category did not // match, then we still want to see this transaction if it's // a transfer if (!categoryMatched && !filter.categoryFilter) categoryMatched = isTransfer; if (isMatchingSplitsEmpty || !(accountMatched && categoryMatched)) { d->m_matchingSplitsCount = 0; return matchingSplits; } if (!d->m_reportAllSplits && !isMatchingSplitsEmpty) { d->m_matchingSplitsCount = 1; if (!d->m_matchOnly) matchingSplits.append(transaction.firstSplit()); } else { d->m_matchingSplitsCount = matchingSplits.count(); } // all filters passed, I guess we have a match // qDebug(" C: %d", m_matchingSplits.count()); return matchingSplits; } QDate MyMoneyTransactionFilter::fromDate() const { Q_D(const MyMoneyTransactionFilter); return d->m_fromDate; } QDate MyMoneyTransactionFilter::toDate() const { Q_D(const MyMoneyTransactionFilter); return d->m_toDate; } bool MyMoneyTransactionFilter::matchText(const MyMoneySplit& s, const MyMoneyAccount& acc) const { Q_D(const MyMoneyTransactionFilter); // check if the text is contained in one of the fields - // memo, value, number, payee, tag, account, date + // memo, value, number, payee, tag, account if (d->m_filterSet.singleFilter.textFilter) { const auto file = MyMoneyFile::instance(); const auto sec = file->security(acc.currencyId()); if (s.memo().contains(d->m_text) || s.shares().formatMoney(acc.fraction(sec)).contains(d->m_text) || s.value().formatMoney(acc.fraction(sec)).contains(d->m_text) || s.number().contains(d->m_text) || (d->m_text.pattern().compare(s.transactionId())) == 0) return !d->m_invertText; if (acc.name().contains(d->m_text)) return !d->m_invertText; if (!s.payeeId().isEmpty() && file->payee(s.payeeId()).name().contains(d->m_text)) return !d->m_invertText; for (const auto& tag : s.tagIdList()) if (file->tag(tag).name().contains(d->m_text)) return !d->m_invertText; return d->m_invertText; } return true; } bool MyMoneyTransactionFilter::matchAmount(const MyMoneySplit& s) const { Q_D(const MyMoneyTransactionFilter); if (d->m_filterSet.singleFilter.amountFilter) { const auto value = s.value().abs(); const auto shares = s.shares().abs(); if ((value < d->m_fromAmount || value > d->m_toAmount) && (shares < d->m_fromAmount || shares > d->m_toAmount)) return false; } return true; } bool MyMoneyTransactionFilter::match(const MyMoneySplit& s) const { const auto& acc = MyMoneyFile::instance()->account(s.accountId()); return matchText(s, acc) && matchAmount(s); } bool MyMoneyTransactionFilter::match(const MyMoneyTransaction& transaction) { Q_D(MyMoneyTransactionFilter); d->m_matchOnly = true; matchingSplits(transaction); d->m_matchOnly = false; return d->m_matchingSplitsCount > 0; } int MyMoneyTransactionFilter::splitState(const MyMoneySplit& split) const { switch (split.reconcileFlag()) { default: case eMyMoney::Split::State::NotReconciled: return (int)eMyMoney::TransactionFilter::State::NotReconciled; case eMyMoney::Split::State::Cleared: return (int)eMyMoney::TransactionFilter::State::Cleared; case eMyMoney::Split::State::Reconciled: return (int)eMyMoney::TransactionFilter::State::Reconciled; case eMyMoney::Split::State::Frozen: return (int)eMyMoney::TransactionFilter::State::Frozen; } } int MyMoneyTransactionFilter::splitType(const MyMoneyTransaction& t, const MyMoneySplit& split, const MyMoneyAccount& acc) const { qDebug() << "SplitType"; if (acc.isIncomeExpense()) return (int)eMyMoney::TransactionFilter::Type::All; if (t.splitCount() == 2) { const auto& splits = t.splits(); const auto file = MyMoneyFile::instance(); const auto& a = splits.at(0).id().compare(split.id()) == 0 ? acc : file->account(splits.at(0).accountId()); const auto& b = splits.at(1).id().compare(split.id()) == 0 ? acc : file->account(splits.at(1).accountId()); qDebug() << "first split: " << splits.at(0).accountId() << "second split: " << splits.at(1).accountId(); if (!a.isIncomeExpense() && !b.isIncomeExpense()) return (int)eMyMoney::TransactionFilter::Type::Transfers; } if (split.value().isPositive()) return (int)eMyMoney::TransactionFilter::Type::Deposits; return (int)eMyMoney::TransactionFilter::Type::Payments; } eMyMoney::TransactionFilter::Validity MyMoneyTransactionFilter::validTransaction(const MyMoneyTransaction& t) const { MyMoneyMoney val; for (const auto& split : t.splits()) val += split.value(); return (val == MyMoneyMoney()) ? eMyMoney::TransactionFilter::Validity::Valid : eMyMoney::TransactionFilter::Validity::Invalid; } bool MyMoneyTransactionFilter::includesCategory(const QString& cat) const { Q_D(const MyMoneyTransactionFilter); return !d->m_filterSet.singleFilter.categoryFilter || d->m_categories.contains(cat); } bool MyMoneyTransactionFilter::includesAccount(const QString& acc) const { Q_D(const MyMoneyTransactionFilter); return !d->m_filterSet.singleFilter.accountFilter || d->m_accounts.contains(acc); } bool MyMoneyTransactionFilter::includesPayee(const QString& pye) const { Q_D(const MyMoneyTransactionFilter); return !d->m_filterSet.singleFilter.payeeFilter || d->m_payees.contains(pye); } bool MyMoneyTransactionFilter::includesTag(const QString& tag) const { Q_D(const MyMoneyTransactionFilter); return !d->m_filterSet.singleFilter.tagFilter || d->m_tags.contains(tag); } bool MyMoneyTransactionFilter::dateFilter(QDate& from, QDate& to) const { Q_D(const MyMoneyTransactionFilter); from = d->m_fromDate; to = d->m_toDate; return d->m_filterSet.singleFilter.dateFilter == 1; } bool MyMoneyTransactionFilter::amountFilter(MyMoneyMoney& from, MyMoneyMoney& to) const { Q_D(const MyMoneyTransactionFilter); from = d->m_fromAmount; to = d->m_toAmount; return d->m_filterSet.singleFilter.amountFilter == 1; } bool MyMoneyTransactionFilter::numberFilter(QString& from, QString& to) const { Q_D(const MyMoneyTransactionFilter); from = d->m_fromNr; to = d->m_toNr; return d->m_filterSet.singleFilter.nrFilter == 1; } bool MyMoneyTransactionFilter::payees(QStringList& list) const { Q_D(const MyMoneyTransactionFilter); auto result = d->m_filterSet.singleFilter.payeeFilter; if (result) { QHashIterator it_payee(d->m_payees); while (it_payee.hasNext()) { it_payee.next(); list += it_payee.key(); } } return result; } bool MyMoneyTransactionFilter::tags(QStringList& list) const { Q_D(const MyMoneyTransactionFilter); auto result = d->m_filterSet.singleFilter.tagFilter; if (result) { QHashIterator it_tag(d->m_tags); while (it_tag.hasNext()) { it_tag.next(); list += it_tag.key(); } } return result; } bool MyMoneyTransactionFilter::accounts(QStringList& list) const { Q_D(const MyMoneyTransactionFilter); auto result = d->m_filterSet.singleFilter.accountFilter; if (result) { QHashIterator it_account(d->m_accounts); while (it_account.hasNext()) { it_account.next(); QString account = it_account.key(); list += account; } } return result; } bool MyMoneyTransactionFilter::categories(QStringList& list) const { Q_D(const MyMoneyTransactionFilter); auto result = d->m_filterSet.singleFilter.categoryFilter; if (result) { QHashIterator it_category(d->m_categories); while (it_category.hasNext()) { it_category.next(); list += it_category.key(); } } return result; } bool MyMoneyTransactionFilter::types(QList& list) const { Q_D(const MyMoneyTransactionFilter); auto result = d->m_filterSet.singleFilter.typeFilter; if (result) { QHashIterator it_type(d->m_types); while (it_type.hasNext()) { it_type.next(); list += it_type.key(); } } return result; } bool MyMoneyTransactionFilter::states(QList& list) const { Q_D(const MyMoneyTransactionFilter); auto result = d->m_filterSet.singleFilter.stateFilter; if (result) { QHashIterator it_state(d->m_states); while (it_state.hasNext()) { it_state.next(); list += it_state.key(); } } return result; } bool MyMoneyTransactionFilter::firstType(int&i) const { Q_D(const MyMoneyTransactionFilter); auto result = d->m_filterSet.singleFilter.typeFilter; if (result) { QHashIterator it_type(d->m_types); if (it_type.hasNext()) { it_type.next(); i = it_type.key(); } } return result; } bool MyMoneyTransactionFilter::firstState(int&i) const { Q_D(const MyMoneyTransactionFilter); auto result = d->m_filterSet.singleFilter.stateFilter; if (result) { QHashIterator it_state(d->m_states); if (it_state.hasNext()) { it_state.next(); i = it_state.key(); } } return result; } bool MyMoneyTransactionFilter::textFilter(QRegExp& exp) const { Q_D(const MyMoneyTransactionFilter); exp = d->m_text; return d->m_filterSet.singleFilter.textFilter == 1; } bool MyMoneyTransactionFilter::isInvertingText() const { Q_D(const MyMoneyTransactionFilter); return d->m_invertText; } void MyMoneyTransactionFilter::setDateFilter(eMyMoney::TransactionFilter::Date range) { QDate from, to; if (translateDateRange(range, from, to)) setDateFilter(from, to); } static int fiscalYearStartMonth = 1; static int fiscalYearStartDay = 1; void MyMoneyTransactionFilter::setFiscalYearStart(int firstMonth, int firstDay) { fiscalYearStartMonth = firstMonth; fiscalYearStartDay = firstDay; } bool MyMoneyTransactionFilter::translateDateRange(eMyMoney::TransactionFilter::Date id, QDate& start, QDate& end) { bool rc = true; int yr = QDate::currentDate().year(); int mon = QDate::currentDate().month(); switch (id) { case eMyMoney::TransactionFilter::Date::All: start = QDate(); end = QDate(); break; case eMyMoney::TransactionFilter::Date::AsOfToday: start = QDate(); end = QDate::currentDate(); break; case eMyMoney::TransactionFilter::Date::CurrentMonth: start = QDate(yr, mon, 1); end = QDate(yr, mon, 1).addMonths(1).addDays(-1); break; case eMyMoney::TransactionFilter::Date::CurrentYear: start = QDate(yr, 1, 1); end = QDate(yr, 12, 31); break; case eMyMoney::TransactionFilter::Date::MonthToDate: start = QDate(yr, mon, 1); end = QDate::currentDate(); break; case eMyMoney::TransactionFilter::Date::YearToDate: start = QDate(yr, 1, 1); end = QDate::currentDate(); break; case eMyMoney::TransactionFilter::Date::YearToMonth: start = QDate(yr, 1, 1); end = QDate(yr, mon, 1).addDays(-1); break; case eMyMoney::TransactionFilter::Date::LastMonth: start = QDate(yr, mon, 1).addMonths(-1); end = QDate(yr, mon, 1).addDays(-1); break; case eMyMoney::TransactionFilter::Date::LastYear: start = QDate(yr, 1, 1).addYears(-1); end = QDate(yr, 12, 31).addYears(-1); break; case eMyMoney::TransactionFilter::Date::Last7Days: start = QDate::currentDate().addDays(-7); end = QDate::currentDate(); break; case eMyMoney::TransactionFilter::Date::Last30Days: start = QDate::currentDate().addDays(-30); end = QDate::currentDate(); break; case eMyMoney::TransactionFilter::Date::Last3Months: start = QDate::currentDate().addMonths(-3); end = QDate::currentDate(); break; case eMyMoney::TransactionFilter::Date::Last6Months: start = QDate::currentDate().addMonths(-6); end = QDate::currentDate(); break; case eMyMoney::TransactionFilter::Date::Last11Months: start = QDate(yr, mon, 1).addMonths(-12); end = QDate(yr, mon, 1).addDays(-1); break; case eMyMoney::TransactionFilter::Date::Last12Months: start = QDate::currentDate().addMonths(-12); end = QDate::currentDate(); break; case eMyMoney::TransactionFilter::Date::Next7Days: start = QDate::currentDate(); end = QDate::currentDate().addDays(7); break; case eMyMoney::TransactionFilter::Date::Next30Days: start = QDate::currentDate(); end = QDate::currentDate().addDays(30); break; case eMyMoney::TransactionFilter::Date::Next3Months: start = QDate::currentDate(); end = QDate::currentDate().addMonths(3); break; case eMyMoney::TransactionFilter::Date::Next6Months: start = QDate::currentDate(); end = QDate::currentDate().addMonths(6); break; case eMyMoney::TransactionFilter::Date::Next12Months: start = QDate::currentDate(); end = QDate::currentDate().addMonths(12); break; case eMyMoney::TransactionFilter::Date::Next18Months: start = QDate::currentDate(); end = QDate::currentDate().addMonths(18); break; case eMyMoney::TransactionFilter::Date::UserDefined: start = QDate(); end = QDate(); break; case eMyMoney::TransactionFilter::Date::Last3ToNext3Months: start = QDate::currentDate().addMonths(-3); end = QDate::currentDate().addMonths(3); break; case eMyMoney::TransactionFilter::Date::CurrentQuarter: start = QDate(yr, mon - ((mon - 1) % 3), 1); end = start.addMonths(3).addDays(-1); break; case eMyMoney::TransactionFilter::Date::LastQuarter: start = QDate(yr, mon - ((mon - 1) % 3), 1).addMonths(-3); end = start.addMonths(3).addDays(-1); break; case eMyMoney::TransactionFilter::Date::NextQuarter: start = QDate(yr, mon - ((mon - 1) % 3), 1).addMonths(3); end = start.addMonths(3).addDays(-1); break; case eMyMoney::TransactionFilter::Date::CurrentFiscalYear: start = QDate(QDate::currentDate().year(), fiscalYearStartMonth, fiscalYearStartDay); if (QDate::currentDate() < start) start = start.addYears(-1); end = start.addYears(1).addDays(-1); break; case eMyMoney::TransactionFilter::Date::LastFiscalYear: start = QDate(QDate::currentDate().year(), fiscalYearStartMonth, fiscalYearStartDay); if (QDate::currentDate() < start) start = start.addYears(-1); start = start.addYears(-1); end = start.addYears(1).addDays(-1); break; case eMyMoney::TransactionFilter::Date::Today: start = QDate::currentDate(); end = QDate::currentDate(); break; default: qWarning("Unknown date identifier %d in MyMoneyTransactionFilter::translateDateRange()", (int)id); rc = false; break; } return rc; } MyMoneyTransactionFilter::FilterSet MyMoneyTransactionFilter::filterSet() const { Q_D(const MyMoneyTransactionFilter); return d->m_filterSet; } void MyMoneyTransactionFilter::removeReference(const QString& id) { Q_D(MyMoneyTransactionFilter); if (d->m_accounts.end() != d->m_accounts.find(id)) { qDebug("%s", qPrintable(QString("Remove account '%1' from report").arg(id))); d->m_accounts.take(id); } else if (d->m_categories.end() != d->m_categories.find(id)) { qDebug("%s", qPrintable(QString("Remove category '%1' from report").arg(id))); d->m_categories.remove(id); } else if (d->m_payees.end() != d->m_payees.find(id)) { qDebug("%s", qPrintable(QString("Remove payee '%1' from report").arg(id))); d->m_payees.remove(id); } else if (d->m_tags.end() != d->m_tags.find(id)) { qDebug("%s", qPrintable(QString("Remove tag '%1' from report").arg(id))); d->m_tags.remove(id); } } diff --git a/kmymoney/mymoney/mymoneytransactionfilter.h b/kmymoney/mymoney/mymoneytransactionfilter.h index 5652dcaf8..bc1fca469 100644 --- a/kmymoney/mymoney/mymoneytransactionfilter.h +++ b/kmymoney/mymoney/mymoneytransactionfilter.h @@ -1,564 +1,565 @@ /* * Copyright 2003-2018 Thomas Baumgart * Copyright 2004 Ace Jones * Copyright 2008-2010 Alvaro Soliverez * Copyright 2017-2018 Łukasz Wojniłowicz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef MYMONEYTRANSACTIONFILTER_H #define MYMONEYTRANSACTIONFILTER_H #include "kmm_mymoney_export.h" // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes class QString; class QDate; template class QList; class MyMoneyMoney; class MyMoneySplit; class MyMoneyAccount; namespace eMyMoney { namespace TransactionFilter { enum class Date; enum class Validity; } } /** * @author Thomas Baumgart * @author Łukasz Wojniłowicz */ class MyMoneyTransaction; class MyMoneyTransactionFilterPrivate; class KMM_MYMONEY_EXPORT MyMoneyTransactionFilter { Q_DECLARE_PRIVATE(MyMoneyTransactionFilter) protected: MyMoneyTransactionFilterPrivate* d_ptr; // name shouldn't colide with the one in mymoneyreport.h public: typedef union { unsigned allFilter; struct { unsigned textFilter : 1; unsigned accountFilter : 1; unsigned payeeFilter : 1; unsigned tagFilter : 1; unsigned categoryFilter : 1; unsigned nrFilter : 1; unsigned dateFilter : 1; unsigned amountFilter : 1; unsigned typeFilter : 1; unsigned stateFilter : 1; unsigned validityFilter : 1; } singleFilter; } FilterSet; /** * This is the standard constructor for a transaction filter. * It creates the object and calls setReportAllSplits() to * report all matching splits as separate entries. Use * setReportAllSplits() to override this behaviour. */ MyMoneyTransactionFilter(); /** * This is a convenience constructor to allow construction of * a simple account filter. It is basically the same as the * following: * * @code * : * MyMoneyTransactionFilter filter; * filter.setReportAllSplits(false); * filter.addAccount(id); * : * @endcode * * @param id reference to account id */ explicit MyMoneyTransactionFilter(const QString& id); MyMoneyTransactionFilter(const MyMoneyTransactionFilter & other); MyMoneyTransactionFilter(MyMoneyTransactionFilter && other); MyMoneyTransactionFilter & operator=(MyMoneyTransactionFilter other); friend void swap(MyMoneyTransactionFilter& first, MyMoneyTransactionFilter& second); virtual ~MyMoneyTransactionFilter(); /** * This method is used to clear the filter. All settings will be * removed. */ void clear(); /** * This method is used to clear the accounts filter only. */ void clearAccountFilter(); /** * This method is used to set the regular expression filter to the value specified * as parameter @p exp. The following text based fields are searched: * * - Memo * - Payee * - Tag * - Category * - Shares / Value * - Number * * @param exp The regular expression that must be found in a transaction * before it is included in the result set. * @param invert If true, value must not be contained in any of the above mentioned fields * */ void setTextFilter(const QRegExp& exp, bool invert = false); /** * This method will add the account with id @p id to the list of matching accounts. * If the list is empty, any transaction will match. * * @param id internal ID of the account */ void addAccount(const QString& id); /** * This is a convenience method and behaves exactly like the above * method but for a list of id's. */ void addAccount(const QStringList& ids); /** * This method will add the category with id @p id to the list of matching categories. * If the list is empty, only transaction with a single asset/liability account will match. * * @param id internal ID of the account */ void addCategory(const QString& id); /** * This is a convenience method and behaves exactly like the above * method but for a list of id's. */ void addCategory(const QStringList& ids); /** * This method sets the date filter to match only transactions with posting dates in * the date range specified by @p from and @p to. If @p from equal QDate() * all transactions with dates prior to @p to match. If @p to equals QDate() * all transactions with posting dates past @p from match. If @p from and @p to * are equal QDate() the filter is not activated and all transactions match. * * @param from from date * @param to to date */ void setDateFilter(const QDate& from, const QDate& to); void setDateFilter(eMyMoney::TransactionFilter::Date range); /** * This method sets the amount filter to match only transactions with * an amount in the range specified by @p from and @p to. * If a specific amount should be searched, @p from and @p to should be * the same value. * * @param from smallest value to match * @param to largest value to match */ void setAmountFilter(const MyMoneyMoney& from, const MyMoneyMoney& to); /** * This method will add the payee with id @p id to the list of matching payees. * If the list is empty, any transaction will match. * * @param id internal id of the payee */ void addPayee(const QString& id); /** * This method will add the tag with id @ta id to the list of matching tags. * If the list is empty, any transaction will match. * * @param id internal id of the tag */ void addTag(const QString& id); /** */ void addType(const int type); /** */ void addValidity(const int type); /** */ void addState(const int state); /** * This method sets the number filter to match only transactions with * a number in the range specified by @p from and @p to. * If a specific number should be searched, @p from and @p to should be * the same value. * * @param from smallest value to match * @param to largest value to match * * @note @p from and @p to can contain alphanumeric text */ void setNumberFilter(const QString& from, const QString& to); /** * This method is used to check a specific transaction against the filter. * The transaction will match the whole filter, if all specified filters - * match. If the filter is cleared using the clear() method, any transaciton - * matches. + * match. If the filter is cleared using the clear() method, any transaction + * matches. Matching splits from the transaction are returned by @ref + * matchingSplits(). * * @param transaction A transaction * * @retval true The transaction matches the filter set * @retval false The transaction does not match at least one of * the filters in the filter set */ bool match(const MyMoneyTransaction& transaction); /** * This method is used to check a specific split against the * text filter. The split will match if all specified and * checked filters match. If the filter is cleared using the clear() * method, any split matches. * * @param sp pointer to the split to be checked * * @retval true The split matches the filter set * @retval false The split does not match at least one of * the filters in the filter set */ bool matchText(const MyMoneySplit& s, const MyMoneyAccount &acc) const; /** * This method is used to check a specific split against the * amount filter. The split will match if all specified and * checked filters match. If the filter is cleared using the clear() * method, any split matches. * - * @param sp pointer to the split to be checked + * @param sp const reference to the split to be checked * * @retval true The split matches the filter set * @retval false The split does not match at least one of * the filters in the filter set */ bool matchAmount(const MyMoneySplit& s) const; /** * Convenience method which actually returns matchText(sp) && matchAmount(sp). */ bool match(const MyMoneySplit& s) const; /** * This method is used to switch the amount of splits reported * by matchingSplits(). If the argument @p report is @p true (the default * if no argument specified) then matchingSplits() will return all * matching splits of the transaction. If @p report is set to @p false, * then only the very first matching split will be returned by * matchingSplits(). * * @param report controls the behaviour of matchingsSplits() as explained above. */ void setReportAllSplits(const bool report = true); void setConsiderCategory(const bool check = true); /** * This method is to avoid returning matching splits list * if only its count is needed * @return count of matching splits */ uint matchingSplitsCount(const MyMoneyTransaction& transaction); /** * This method returns a list of the matching splits for the filter. * If m_reportAllSplits is set to false, then only the very first * split will be returned. Use setReportAllSplits() to change the * behaviour. * * @return reference list of MyMoneySplit objects containing the * matching splits. If multiple splits match, only the first * one will be returned. * * @note an empty list will be returned, if the filter only required * to check the data contained in the MyMoneyTransaction * object (e.g. posting-date, state, etc.). * * @note The constructors set m_reportAllSplits differently. Please * see the documentation of the constructors MyMoneyTransactionFilter() * and MyMoneyTransactionFilter(const QString&) for details. */ QVector matchingSplits(const MyMoneyTransaction& transaction); /** * This method returns the from date set in the filter. If * no value has been set up for this filter, then QDate() is * returned. * * @return returns m_fromDate */ QDate fromDate() const; /** * This method returns the to date set in the filter. If * no value has been set up for this filter, then QDate() is * returned. * * @return returns m_toDate */ QDate toDate() const; /** * This method is used to return information about the * presence of a specific category in the category filter. * The category in question is included in the filter set, * if it has been set or no category filter is set. * * @param cat id of category in question * @return true if category is in filter set, false otherwise */ bool includesCategory(const QString& cat) const; /** * This method is used to return information about the * presence of a specific account in the account filter. * The account in question is included in the filter set, * if it has been set or no account filter is set. * * @param acc id of account in question * @return true if account is in filter set, false otherwise */ bool includesAccount(const QString& acc) const; /** * This method is used to return information about the * presence of a specific payee in the account filter. * The payee in question is included in the filter set, * if it has been set or no account filter is set. * * @param pye id of payee in question * @return true if payee is in filter set, false otherwise */ bool includesPayee(const QString& pye) const; /** * This method is used to return information about the * presence of a specific tag in the account filter. * The tag in question is included in the filter set, * if it has been set or no account filter is set. * * @param tag id of tag in question * @return true if tag is in filter set, false otherwise */ bool includesTag(const QString& tag) const; /** * This method is used to return information about the * presence of a date filter. * * @param from result value for the beginning of the date range * @param to result value for the end of the date range * @return true if a date filter is set */ bool dateFilter(QDate& from, QDate& to) const; /** * This method is used to return information about the * presence of an amount filter. * * @param from result value for the low end of the amount range * @param to result value for the high end of the amount range * @return true if an amount filter is set */ bool amountFilter(MyMoneyMoney& from, MyMoneyMoney& to) const; /** * This method is used to return information about the * presence of an number filter. * * @param from result value for the low end of the number range * @param to result value for the high end of the number range * @return true if a number filter is set */ bool numberFilter(QString& from, QString& to) const; /** * This method returns whether a payee filter has been set, * and if so, it returns all the payees set in the filter. * * @param list list to append payees into * @return return true if a payee filter has been set */ bool payees(QStringList& list) const; /** * This method returns whether a tag filter has been set, * and if so, it returns all the tags set in the filter. * * @param list list to append tags into * @return return true if a tag filter has been set */ bool tags(QStringList& list) const; /** * This method returns whether an account filter has been set, * and if so, it returns all the accounts set in the filter. * * @param list list to append accounts into * @return return true if an account filter has been set */ bool accounts(QStringList& list) const; /** * This method returns whether a category filter has been set, * and if so, it returns all the categories set in the filter. * * @param list list to append categories into * @return return true if a category filter has been set */ bool categories(QStringList& list) const; /** * This method returns whether a type filter has been set, * and if so, it returns the first type in the filter. * * @param i int to replace with first type filter, untouched otherwise * @return return true if a type filter has been set */ bool firstType(int& i) const; bool types(QList& list) const; /** * This method returns whether a state filter has been set, * and if so, it returns the first state in the filter. * * @param i reference to int to replace with first state filter, untouched otherwise * @return return true if a state filter has been set */ bool firstState(int& i) const; bool states(QList& list) const; /** * This method returns whether a text filter has been set, * and if so, it returns the text filter. * * @param text regexp to replace with text filter, or blank if none set * @return return true if a text filter has been set */ bool textFilter(QRegExp& text) const; /** * This method returns whether the text filter should return * that DO NOT contain the text */ bool isInvertingText() const; /** * This method translates a plain-language date range into QDate * start & end * * @param range Plain-language range of dates, e.g. 'CurrentYear' * @param start QDate will be set to corresponding to the first date in @p range * @param end QDate will be set to corresponding to the last date in @p range * @return return true if a range was successfully set, or false if @p range was invalid */ static bool translateDateRange(eMyMoney::TransactionFilter::Date range, QDate& start, QDate& end); static void setFiscalYearStart(int firstMonth, int firstDay); FilterSet filterSet() const; /** * This member removes all references to object identified by @p id. Used * to remove objects which are about to be removed from the engine. */ void removeReference(const QString& id); private: /** * This is a conversion tool from eMyMoney::Split::State * to MyMoneyTransactionFilter::stateE types * * @param split reference to split in question * * @return converted reconcile flag of the split passed as parameter */ int splitState(const MyMoneySplit& split) const; /** * This is a conversion tool from MyMoneySplit::action * to MyMoneyTransactionFilter::typeE types * * @param t reference to transaction * @param split reference to split in question * * @return converted action of the split passed as parameter */ int splitType(const MyMoneyTransaction& t, const MyMoneySplit& split, const MyMoneyAccount &acc) const; /** * This method checks if a transaction is valid or not. A transaction * is considered valid, if the sum of all splits is zero, invalid otherwise. * * @param transaction reference to transaction to be checked * @retval valid transaction is valid * @retval invalid transaction is invalid */ eMyMoney::TransactionFilter::Validity validTransaction(const MyMoneyTransaction& transaction) const; }; inline void swap(MyMoneyTransactionFilter& first, MyMoneyTransactionFilter& second) // krazy:exclude=inline { using std::swap; swap(first.d_ptr, second.d_ptr); } inline MyMoneyTransactionFilter::MyMoneyTransactionFilter(MyMoneyTransactionFilter && other) : MyMoneyTransactionFilter() // krazy:exclude=inline { swap(*this, other); } inline MyMoneyTransactionFilter & MyMoneyTransactionFilter::operator=(MyMoneyTransactionFilter other) // krazy:exclude=inline { swap(*this, other); return *this; } /** * Make it possible to hold @ref MyMoneyTransactionFilter objects inside @ref QVariant objects. */ Q_DECLARE_METATYPE(MyMoneyTransactionFilter) #endif diff --git a/kmymoney/mymoney/tests/mymoneytransactionfilter-test.cpp b/kmymoney/mymoney/tests/mymoneytransactionfilter-test.cpp new file mode 100644 index 000000000..d9ee6b068 --- /dev/null +++ b/kmymoney/mymoney/tests/mymoneytransactionfilter-test.cpp @@ -0,0 +1,787 @@ +/* + * Copyright 2018 Ralf Habacker + * Copyright 2018 Thomas Baumgart + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "mymoneytransactionfilter-test.h" + +#include + +#include "mymoneyenums.h" +#include "mymoneytransactionfilter.h" +#include "mymoneyfile.h" +#include "mymoneystoragemgr.h" +#include "mymoneyaccount.h" +#include "mymoneypayee.h" +#include "mymoneysecurity.h" +#include "mymoneytag.h" +#include "mymoneytransaction.h" +#include "mymoneysplit.h" +#include "mymoneyexception.h" + +// uses helper functions from reports tests +#include "tests/testutilities.h" +using namespace test; + + +QTEST_GUILESS_MAIN(MyMoneyTransactionFilterTest) + + +// using namespace std; + +void MyMoneyTransactionFilterTest::init() +{ + storage = new MyMoneyStorageMgr; + file = MyMoneyFile::instance(); + file->attachStorage(storage); + + MyMoneyFileTransaction ft; + file->addCurrency(MyMoneySecurity("USD", "US Dollar", "$")); + file->setBaseCurrency(file->currency("USD")); + + MyMoneyPayee payeeTest("Payee 10.2"); + file->addPayee(payeeTest); + payeeId = payeeTest.id(); + + MyMoneyTag tag("Tag 10.2"); + file->addTag(tag); + tagIdList << tag.id(); + + QString acAsset = MyMoneyFile::instance()->asset().id(); + QString acExpense = (MyMoneyFile::instance()->expense().id()); + QString acIncome = (MyMoneyFile::instance()->income().id()); + acCheckingId = makeAccount("Account 10.2", eMyMoney::Account::Type::Checkings, MyMoneyMoney(0.0), QDate(2004, 1, 1), acAsset); + acExpenseId = makeAccount("Expense", eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); + acIncomeId = makeAccount("Expense", eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acIncome); + + ft.commit(); +} + +void MyMoneyTransactionFilterTest::cleanup() +{ + file->detachStorage(storage); + delete storage; +} + +void MyMoneyTransactionFilterTest::testMatchAmount() +{ + MyMoneySplit split; + split.setShares(MyMoneyMoney(123.20)); + + MyMoneyTransactionFilter filter; + QCOMPARE(filter.matchAmount(split), true); + + filter.setAmountFilter(MyMoneyMoney("123.0"), MyMoneyMoney("124.0")); + QCOMPARE(filter.matchAmount(split), true); + filter.setAmountFilter(MyMoneyMoney("120.0"), MyMoneyMoney("123.0")); + QCOMPARE(filter.matchAmount(split), false); +} + +void MyMoneyTransactionFilterTest::testMatchText() +{ + MyMoneySplit split; + MyMoneyTransactionFilter filter; + MyMoneyAccount account = file->account(acCheckingId); + + // no filter + QCOMPARE(filter.matchText(split, account), true); + + filter.setTextFilter(QRegExp("10.2"), false); + MyMoneyTransactionFilter filterInvert; + filterInvert.setTextFilter(QRegExp("10.2"), true); + MyMoneyTransactionFilter filterNotFound; + filterNotFound.setTextFilter(QRegExp("10.5"), false); + + // memo + split.setMemo("10.2"); + QCOMPARE(filter.matchText(split, account), true); + QCOMPARE(filterInvert.matchText(split, account), false); + QCOMPARE(filterNotFound.matchText(split, account), false); + split.setMemo(QString()); + // payee + split.setPayeeId(payeeId); + QCOMPARE(filter.matchText(split, account), true); + QCOMPARE(filterInvert.matchText(split, account), false); + QCOMPARE(filterNotFound.matchText(split, account), false); + split.setPayeeId(QString()); + // tag + split.setTagIdList(tagIdList); + QCOMPARE(filter.matchText(split, account), true); + QCOMPARE(filterInvert.matchText(split, account), false); + QCOMPARE(filterNotFound.matchText(split, account), false); + split.setTagIdList(QStringList()); + // value + split.setValue(MyMoneyMoney("10.2")); + QCOMPARE(filter.matchText(split, account), true); + QCOMPARE(filterInvert.matchText(split, account), false); + QCOMPARE(filterNotFound.matchText(split, account), false); + split.setValue(MyMoneyMoney()); + // number + split.setNumber("10.2"); + QCOMPARE(filter.matchText(split, account), true); + QCOMPARE(filterInvert.matchText(split, account), false); + QCOMPARE(filterNotFound.matchText(split, account), false); + split.setNumber("0.0"); + // transaction id + split.setTransactionId("10.2"); + QCOMPARE(filter.matchText(split, account), true); + QCOMPARE(filterInvert.matchText(split, account), false); + QCOMPARE(filterNotFound.matchText(split, account), false); + split.setTransactionId("0.0"); + // account + split.setAccountId(acCheckingId); + QCOMPARE(filter.matchText(split, account), true); + QCOMPARE(filterInvert.matchText(split, account), false); + QCOMPARE(filterNotFound.matchText(split, account), false); +} + +void MyMoneyTransactionFilterTest::testMatchSplit() +{ + qDebug() << "returns matchText() || matchAmount(), which are already tested"; +} + +void MyMoneyTransactionFilterTest::testMatchTransactionAll() +{ + MyMoneySplit split; + split.setAccountId(acCheckingId); + split.setShares(MyMoneyMoney(123.00)); + + MyMoneySplit split2; + split2.setAccountId(acExpenseId); + split2.setShares(MyMoneyMoney(123.00)); + + MyMoneyTransaction transaction; + transaction.setPostDate(QDate(2014, 1, 2)); + transaction.addSplit(split); + transaction.addSplit(split2); + + MyMoneyTransactionFilter filter; + filter.setReportAllSplits(true); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 2); + + filter.setReportAllSplits(false); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 1); +} + +void MyMoneyTransactionFilterTest::testMatchTransactionAccount() +{ + MyMoneySplit split; + split.setAccountId(acCheckingId); + split.setShares(MyMoneyMoney(123.00)); + + MyMoneySplit split2; + split2.setAccountId(acExpenseId); + split2.setShares(MyMoneyMoney(123.00)); + + MyMoneyTransaction transaction; + transaction.setPostDate(QDate(2014, 1, 2)); + transaction.addSplit(split); + transaction.addSplit(split2); + + MyMoneyTransactionFilter filter; + filter.addAccount(acCheckingId); + filter.setReportAllSplits(true); + filter.setConsiderCategory(false); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 1); + + filter.setReportAllSplits(false); + filter.setConsiderCategory(false); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 1); + + filter.setReportAllSplits(false); + filter.setConsiderCategory(true); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 1); + + filter.setReportAllSplits(true); + filter.setConsiderCategory(true); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 1); + filter.clear(); +} + +void MyMoneyTransactionFilterTest::testMatchTransactionCategory() +{ + MyMoneySplit split; + split.setAccountId(acCheckingId); + split.setShares(MyMoneyMoney(123.00)); + + MyMoneySplit split2; + split2.setAccountId(acExpenseId); + split2.setShares(MyMoneyMoney(123.00)); + + MyMoneyTransaction transaction; + transaction.setPostDate(QDate(2014, 1, 2)); + transaction.addSplit(split); + transaction.addSplit(split2); + + MyMoneyTransactionFilter filter; + filter.addCategory(acExpenseId); + filter.setReportAllSplits(true); + filter.setConsiderCategory(true); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 2); + + filter.setConsiderCategory(false); + QVERIFY(!filter.match(transaction)); +} + +void MyMoneyTransactionFilterTest::testMatchTransactionDate() +{ + MyMoneySplit split; + split.setAccountId(acCheckingId); + split.setShares(MyMoneyMoney(123.00)); + + MyMoneySplit split2; + split2.setAccountId(acExpenseId); + split2.setShares(MyMoneyMoney(123.00)); + + MyMoneyTransaction transaction; + transaction.setPostDate(QDate(2014, 1, 2)); + transaction.addSplit(split); + transaction.addSplit(split2); + + MyMoneyTransactionFilter filter; + filter.setReportAllSplits(true); + filter.setDateFilter(QDate(2014, 1, 1), QDate(2014, 1, 3)); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 2); + + filter.setReportAllSplits(false); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 1); + + filter.setDateFilter(QDate(2014, 1, 3), QDate(2014, 1, 5)); + QVERIFY(!filter.match(transaction)); +} + +void setupTransactionForNumber(MyMoneyTransaction &transaction, const QString &accountId) +{ + MyMoneySplit split; + split.setAccountId(accountId); + split.setShares(MyMoneyMoney(123.00)); + split.setNumber("1"); + split.setMemo("1"); + + MyMoneySplit split2; + split2.setAccountId(accountId); + split2.setShares(MyMoneyMoney(1.00)); + split2.setNumber("2"); + split2.setMemo("2"); + + MyMoneySplit split3; + split3.setAccountId(accountId); + split3.setShares(MyMoneyMoney(100.00)); + split3.setNumber("3"); + split3.setMemo("3"); + + MyMoneySplit split4; + split4.setAccountId(accountId); + split4.setShares(MyMoneyMoney(22.00)); + split4.setNumber("4"); + split4.setMemo("4"); + + transaction.setPostDate(QDate(2014, 1, 2)); + transaction.addSplit(split); + transaction.addSplit(split2); + transaction.addSplit(split3); + transaction.addSplit(split4); +} + +void runtTestMatchTransactionNumber(MyMoneyTransaction &transaction, MyMoneyTransactionFilter &filter) +{ + // return all matching splits + filter.setReportAllSplits(true); + + filter.setNumberFilter("", ""); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 4); + + filter.setNumberFilter("1", ""); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 4); + + filter.setNumberFilter("", "4"); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 4); + + filter.setNumberFilter("1", "4"); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 4); + + filter.setNumberFilter("1", "2"); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 2); + + // do not return all matching splits + filter.setReportAllSplits(false); + + filter.setNumberFilter("1", "4"); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 1); + + filter.setNumberFilter("1", "2"); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 1); +} + +void MyMoneyTransactionFilterTest::testMatchTransactionNumber() +{ + MyMoneyTransaction transaction; + setupTransactionForNumber(transaction, acCheckingId); + + MyMoneyTransactionFilter filter; + runtTestMatchTransactionNumber(transaction, filter); + + transaction.clear(); + setupTransactionForNumber(transaction, acExpenseId); + + filter.clear(); + runtTestMatchTransactionNumber(transaction, filter); +} + +void MyMoneyTransactionFilterTest::testMatchTransactionPayee() +{ + MyMoneySplit split; + split.setAccountId(acCheckingId); + split.setShares(MyMoneyMoney(123.00)); + split.setPayeeId(payeeId); + + MyMoneySplit split2; + split2.setAccountId(acCheckingId); + split2.setShares(MyMoneyMoney(124.00)); + + MyMoneyTransaction transaction; + transaction.setPostDate(QDate(2014, 1, 2)); + transaction.addSplit(split); + transaction.addSplit(split2); + + MyMoneyTransactionFilter filter; + filter.addPayee(payeeId); + + filter.setReportAllSplits(true); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 1); + + filter.setReportAllSplits(false); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 1); + + // check no category support + MyMoneySplit split3; + split3.setAccountId(acExpenseId); + split3.setShares(MyMoneyMoney(120.00)); + split3.setPayeeId(payeeId); + + MyMoneyTransaction transaction2; + transaction2.setPostDate(QDate(2014, 1, 2)); + transaction2.addSplit(split3); + + filter.setReportAllSplits(true); + QVERIFY(!filter.match(transaction2)); + QCOMPARE(filter.matchingSplits(transaction2).size(), 0); + + qDebug() << "payee on categories could not be tested"; +} + +void MyMoneyTransactionFilterTest::testMatchTransactionState() +{ + MyMoneySplit split; + split.setAccountId(acCheckingId); + split.setShares(MyMoneyMoney(123.00)); + split.setReconcileFlag(eMyMoney::Split::State::NotReconciled); + + MyMoneySplit split2; + split2.setAccountId(acCheckingId); + split2.setShares(MyMoneyMoney(1.00)); + split2.setReconcileFlag(eMyMoney::Split::State::Cleared); + + MyMoneySplit split3; + split3.setAccountId(acCheckingId); + split3.setShares(MyMoneyMoney(100.00)); + split3.setReconcileFlag(eMyMoney::Split::State::Reconciled); + + MyMoneySplit split4; + split4.setAccountId(acCheckingId); + split4.setShares(MyMoneyMoney(22.00)); + split4.setReconcileFlag(eMyMoney::Split::State::Frozen); + + MyMoneyTransaction transaction; + transaction.setPostDate(QDate(2014, 1, 2)); + transaction.addSplit(split); + transaction.addSplit(split2); + transaction.addSplit(split3); + transaction.addSplit(split4); + + MyMoneyTransactionFilter filter; + filter.setReportAllSplits(true); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 4); + + // all states + filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled); + filter.addState((int)eMyMoney::TransactionFilter::State::Cleared); + filter.addState((int)eMyMoney::TransactionFilter::State::Reconciled); + filter.addState((int)eMyMoney::TransactionFilter::State::Frozen); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 4); + + // single state + filter.clear(); + filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 1); + filter.clear(); + + filter.addState((int)eMyMoney::TransactionFilter::State::Cleared); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 1); + filter.clear(); + + filter.addState((int)eMyMoney::TransactionFilter::State::Reconciled); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 1); + filter.clear(); + + filter.addState((int)eMyMoney::TransactionFilter::State::Frozen); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 1); + + // check no category support + MyMoneySplit split5; + split5.setAccountId(acCheckingId); + split5.setShares(MyMoneyMoney(22.00)); + split5.setReconcileFlag(eMyMoney::Split::State::Frozen); + + MyMoneyTransaction transaction2; + transaction.setPostDate(QDate(2014, 1, 2)); + transaction.addSplit(split5); + + filter.clear(); + filter.setReportAllSplits(true); + filter.addState((int)eMyMoney::TransactionFilter::State::Frozen); + QVERIFY(!filter.match(transaction2)); + QCOMPARE(filter.matchingSplits(transaction2).size(), 0); + + qDebug() << "states on categories could not be tested"; +} + +void MyMoneyTransactionFilterTest::testMatchTransactionTag() +{ + MyMoneySplit split; + split.setAccountId(acCheckingId); + split.setShares(MyMoneyMoney(123.00)); + split.setTagIdList(tagIdList); + + MyMoneySplit split2; + split2.setAccountId(acExpenseId); + split2.setShares(MyMoneyMoney(123.00)); + split2.setTagIdList(tagIdList); + + MyMoneySplit split3; + split3.setAccountId(acCheckingId); + split3.setShares(MyMoneyMoney(10.00)); + split3.setTagIdList(tagIdList); + + MyMoneyTransaction transaction; + transaction.setPostDate(QDate(2014, 1, 2)); + transaction.addSplit(split); + transaction.addSplit(split2); + transaction.addSplit(split3); + + MyMoneyTransactionFilter filter; + filter.addTag(tagIdList.first()); + filter.setReportAllSplits(true); + QVERIFY(filter.match(transaction)); + // -1 because categories are not supported yet + QCOMPARE(filter.matchingSplits(transaction).size(), 2); + + filter.setReportAllSplits(false); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 1); + + // check no category support + MyMoneySplit split4; + split4.setAccountId(acExpenseId); + split4.setShares(MyMoneyMoney(123.00)); + split4.setTagIdList(tagIdList); + + MyMoneyTransaction transaction2; + transaction2.setPostDate(QDate(2014, 1, 2)); + transaction2.addSplit(split4); + + filter.setReportAllSplits(true); + QVERIFY(!filter.match(transaction2)); + QCOMPARE(filter.matchingSplits(transaction2).size(), 0); + + qDebug() << "tags on categories could not be tested"; +} + +void MyMoneyTransactionFilterTest::testMatchTransactionTypeAllTypes() +{ + /* + alltypes + - account group == MyMoneyAccount::Income || + - account group == MyMoneyAccount::Expense + */ + MyMoneySplit split; + split.setAccountId(acExpenseId); + split.setValue(MyMoneyMoney(123.00)); + + MyMoneySplit split2; + split2.setAccountId(acIncomeId); + split2.setValue(MyMoneyMoney(-123.00)); + + MyMoneyTransaction transaction; + transaction.setPostDate(QDate(2014, 1, 2)); + transaction.addSplit(split); + transaction.addSplit(split2); + + MyMoneyTransactionFilter filter; + filter.setReportAllSplits(true); + + // all splits + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 2); + + filter.addType((int)eMyMoney::TransactionFilter::State::All); + qDebug() << "MyMoneyTransactionFilter::allTypes could not be tested"; + qDebug() << "because type filter does not work with categories"; + + QVERIFY(!filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 0); + + // ! alltypes + MyMoneySplit split3; + split3.setAccountId(acCheckingId); + split3.setValue(MyMoneyMoney(-123.00)); + + MyMoneyTransaction transaction2; + transaction2.addSplit(split3); + + QVERIFY(!filter.match(transaction2)); + QCOMPARE(filter.matchingSplits(transaction2).size(), 0); +} + +void MyMoneyTransactionFilterTest::testMatchTransactionTypeDeposits() +{ + // deposits - split value is positive + MyMoneySplit split; + split.setAccountId(acCheckingId); + split.setValue(MyMoneyMoney(123.00)); + + MyMoneyTransaction transaction; + transaction.setPostDate(QDate(2014, 1, 2)); + transaction.addSplit(split); + + MyMoneyTransactionFilter filter; + filter.setReportAllSplits(true); + + // all splits + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 1); + + // deposits + filter.addType((int)eMyMoney::TransactionFilter::Type::Deposits); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 1); + + // no deposits + MyMoneySplit split2; + split2.setAccountId(acCheckingId); + split2.setValue(MyMoneyMoney(-123.00)); + + MyMoneyTransaction transaction2; + transaction2.setPostDate(QDate(2014, 1, 2)); + transaction2.addSplit(split2); + + QVERIFY(!filter.match(transaction2)); + QCOMPARE(filter.matchingSplits(transaction2).size(), 0); +} + +void MyMoneyTransactionFilterTest::testMatchTransactionTypePayments() +{ + /* + payments + - account group != MyMoneyAccount::Income + - account group != MyMoneyAccount::Expense + - split value is not positive + - number of splits != 2 + */ + MyMoneySplit split; + split.setAccountId(acCheckingId); + split.setValue(MyMoneyMoney(-123.00)); + + MyMoneyTransaction transaction; + transaction.setPostDate(QDate(2014, 1, 2)); + transaction.addSplit(split); + + MyMoneyTransactionFilter filter; + filter.setReportAllSplits(true); + + // all splits + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 1); + + // valid payments + filter.addType((int)eMyMoney::TransactionFilter::Type::Payments); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 1); + + // no payments + // check number of splits != 2 + MyMoneySplit split2; + split2.setAccountId(acCheckingId); + split2.setValue(MyMoneyMoney(-123.00)); + transaction.addSplit(split2); + + QVERIFY(!filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 0); + + // split value is not positive + MyMoneySplit split3; + split3.setAccountId(acCheckingId); + split3.setValue(MyMoneyMoney(123.00)); + transaction.addSplit(split3); + + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 2); + + // account group != MyMoneyAccount::Income && account group != MyMoneyAccount::Expense + MyMoneySplit split4; + split4.setAccountId(acExpenseId); + split4.setValue(MyMoneyMoney(-124.00)); + transaction.addSplit(split4); + + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 2); +} + +void MyMoneyTransactionFilterTest::testMatchTransactionTypeTransfers() +{ + /* + check transfers + - number of splits == 2 + - account group != MyMoneyAccount::Income + - account group != MyMoneyAccount::Expense + */ + MyMoneySplit split; + split.setAccountId(acCheckingId); + split.setValue(MyMoneyMoney(123.00)); + + MyMoneySplit split2; + split2.setAccountId(acCheckingId); + split2.setValue(MyMoneyMoney(-123.00)); + + MyMoneySplit split3; + split3.setAccountId(acCheckingId); + split3.setValue(MyMoneyMoney(-123.00)); + + MyMoneyTransaction transaction; + transaction.setPostDate(QDate(2014, 1, 2)); + transaction.addSplit(split); + transaction.addSplit(split2); + + MyMoneyTransactionFilter filter; + filter.setReportAllSplits(true); + + // all splits + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 2); + + filter.addType((int)eMyMoney::TransactionFilter::Type::Transfers); + + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 2); + + // transfers - invalid number of counts + transaction.addSplit(split3); + QVERIFY(!filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 0); + + // transfers - invalid account + MyMoneySplit split4; + split4.setAccountId(acIncomeId); + split4.setValue(MyMoneyMoney(-123.00)); + + MyMoneySplit split5; + split5.setAccountId(acCheckingId); + split5.setValue(MyMoneyMoney(123.00)); + + MyMoneyTransaction transaction2; + transaction2.setPostDate(QDate(2014, 1, 2)); + transaction2.addSplit(split4); + transaction2.addSplit(split5); + + QVERIFY(!filter.match(transaction2)); + QCOMPARE(filter.matchingSplits(transaction2).size(), 0); +} + +void MyMoneyTransactionFilterTest::testMatchTransactionValidity() +{ + MyMoneySplit split; + split.setAccountId(acCheckingId); + split.setValue(MyMoneyMoney(123.00)); + + MyMoneySplit split2; + split2.setAccountId(acExpenseId); + split2.setValue(MyMoneyMoney(-123.00)); + + MyMoneyTransaction transaction; + transaction.setPostDate(QDate(2014, 1, 2)); + transaction.addSplit(split); + transaction.addSplit(split2); + + // check valid transaction + MyMoneyTransactionFilter filter; + filter.addValidity((int)eMyMoney::TransactionFilter::Validity::Valid); + filter.setReportAllSplits(true); + + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 2); + + filter.setReportAllSplits(false); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 1); + + // check invalid transaction + filter.clear(); + filter.addValidity((int)eMyMoney::TransactionFilter::Validity::Invalid); + filter.setReportAllSplits(true); + + QVERIFY(!filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 0); + + // add split to make transaction invalid + MyMoneySplit split3; + split3.setAccountId(acExpenseId); + split3.setValue(MyMoneyMoney(-10.00)); + transaction.addSplit(split3); + + filter.setReportAllSplits(true); + QVERIFY(filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 3); + + filter.clear(); + filter.addValidity((int)eMyMoney::TransactionFilter::Validity::Valid); + + QVERIFY(!filter.match(transaction)); + QCOMPARE(filter.matchingSplits(transaction).size(), 0); +} diff --git a/kmymoney/mymoney/tests/mymoneytransactionfilter-test.h b/kmymoney/mymoney/tests/mymoneytransactionfilter-test.h new file mode 100644 index 000000000..96c3e87cf --- /dev/null +++ b/kmymoney/mymoney/tests/mymoneytransactionfilter-test.h @@ -0,0 +1,58 @@ +/* + * Copyright 2018 Ralf Habacker + * Copyright 2018 Thomas Baumgart + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MYMONEYTRANSACTIONFILTERTEST_H +#define MYMONEYTRANSACTIONFILTERTEST_H + +#include +class MyMoneyStorageMgr; +class MyMoneyFile; + +class MyMoneyTransactionFilterTest : public QObject +{ + Q_OBJECT +private slots: + void init(); + void cleanup(); + void testMatchAmount(); + void testMatchText(); + void testMatchSplit(); + void testMatchTransactionAll(); + void testMatchTransactionAccount(); + void testMatchTransactionCategory(); + void testMatchTransactionDate(); + void testMatchTransactionNumber(); + void testMatchTransactionPayee(); + void testMatchTransactionState(); + void testMatchTransactionTag(); + void testMatchTransactionTypeAllTypes(); + void testMatchTransactionTypeDeposits(); + void testMatchTransactionTypePayments(); + void testMatchTransactionTypeTransfers(); + void testMatchTransactionValidity(); +private: + QString payeeId; + QList tagIdList; + QString acCheckingId; + QString acExpenseId; + QString acIncomeId; + MyMoneyStorageMgr* storage; + MyMoneyFile* file; +}; + +#endif