diff --git a/kmymoney/mymoney/mymoneytransactionfilter.cpp b/kmymoney/mymoney/mymoneytransactionfilter.cpp index d37209274..e35350c16 100644 --- a/kmymoney/mymoney/mymoneytransactionfilter.cpp +++ b/kmymoney/mymoney/mymoneytransactionfilter.cpp @@ -1,934 +1,942 @@ /*************************************************************************** mymoneytransactionfilter.cpp - description ------------------- begin : Fri Aug 22 2003 copyright : (C) 2003 by Thomas Baumgart email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "mymoneytransactionfilter.h" // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyfile.h" MyMoneyTransactionFilter::MyMoneyTransactionFilter() { m_filterSet.allFilter = 0; m_reportAllSplits = true; m_considerCategory = true; + m_considerCategorySplits = false; m_invertText = false; } MyMoneyTransactionFilter::MyMoneyTransactionFilter(const QString& id) { m_filterSet.allFilter = 0; m_reportAllSplits = false; m_considerCategory = false; + m_considerCategorySplits = false; m_invertText = false; addAccount(id); // addCategory(id); } MyMoneyTransactionFilter::~MyMoneyTransactionFilter() { } void MyMoneyTransactionFilter::clear() { m_filterSet.allFilter = 0; m_invertText = false; m_accounts.clear(); m_categories.clear(); m_payees.clear(); m_tags.clear(); m_types.clear(); m_states.clear(); m_validity.clear(); m_matchingSplits.clear(); m_fromDate = QDate(); m_toDate = QDate(); } void MyMoneyTransactionFilter::clearAccountFilter() { m_filterSet.singleFilter.accountFilter = 0; m_accounts.clear(); } void MyMoneyTransactionFilter::setTextFilter(const QRegExp& text, bool invert) { m_filterSet.singleFilter.textFilter = 1; m_invertText = invert; m_text = text; } void MyMoneyTransactionFilter::addAccount(const QStringList& ids) { QStringList::ConstIterator it; m_filterSet.singleFilter.accountFilter = 1; for (it = ids.begin(); it != ids.end(); ++it) addAccount(*it); } void MyMoneyTransactionFilter::addAccount(const QString& id) { if (!m_accounts.isEmpty() && !id.isEmpty()) { if (m_accounts.find(id) != m_accounts.end()) return; } m_filterSet.singleFilter.accountFilter = 1; if (!id.isEmpty()) m_accounts.insert(id, ""); } void MyMoneyTransactionFilter::addCategory(const QStringList& ids) { QStringList::ConstIterator it; m_filterSet.singleFilter.categoryFilter = 1; for (it = ids.begin(); it != ids.end(); ++it) addCategory(*it); } void MyMoneyTransactionFilter::addCategory(const QString& id) { if (!m_categories.isEmpty() && !id.isEmpty()) { if (m_categories.end() != m_categories.find(id)) return; } m_filterSet.singleFilter.categoryFilter = 1; if (!id.isEmpty()) m_categories.insert(id, ""); } void MyMoneyTransactionFilter::setDateFilter(const QDate& from, const QDate& to) { m_filterSet.singleFilter.dateFilter = from.isValid() | to.isValid(); m_fromDate = from; m_toDate = to; } void MyMoneyTransactionFilter::setAmountFilter(const MyMoneyMoney& from, const MyMoneyMoney& to) { m_filterSet.singleFilter.amountFilter = 1; m_fromAmount = from.abs(); m_toAmount = to.abs(); // make sure that the user does not try to fool us ;-) if (from > to) { MyMoneyMoney tmp = m_fromAmount; m_fromAmount = m_toAmount; m_toAmount = tmp; } } void MyMoneyTransactionFilter::addPayee(const QString& id) { if (!m_payees.isEmpty() && !id.isEmpty()) { if (m_payees.find(id) != m_payees.end()) return; } m_filterSet.singleFilter.payeeFilter = 1; if (!id.isEmpty()) m_payees.insert(id, ""); } void MyMoneyTransactionFilter::addTag(const QString& id) { if (!m_tags.isEmpty() && !id.isEmpty()) { if (m_tags.find(id) != m_tags.end()) return; } m_filterSet.singleFilter.tagFilter = 1; if (!id.isEmpty()) m_tags.insert(id, ""); } void MyMoneyTransactionFilter::addType(const int type) { if (!m_types.isEmpty()) { if (m_types.find(type) != m_types.end()) return; } m_filterSet.singleFilter.typeFilter = 1; m_types.insert(type, ""); } void MyMoneyTransactionFilter::addState(const int state) { if (!m_states.isEmpty()) { if (m_states.find(state) != m_states.end()) return; } m_filterSet.singleFilter.stateFilter = 1; m_states.insert(state, ""); } void MyMoneyTransactionFilter::addValidity(const int type) { if (!m_validity.isEmpty()) { if (m_validity.find(type) != m_validity.end()) return; } m_filterSet.singleFilter.validityFilter = 1; m_validity.insert(type, ""); } void MyMoneyTransactionFilter::setNumberFilter(const QString& from, const QString& to) { m_filterSet.singleFilter.nrFilter = 1; m_fromNr = from; m_toNr = to; } void MyMoneyTransactionFilter::setReportAllSplits(const bool report) { m_reportAllSplits = report; } +void MyMoneyTransactionFilter::setConsiderCategorySplits(const bool check) +{ + m_considerCategorySplits = check; +} + void MyMoneyTransactionFilter::setConsiderCategory(const bool check) { m_considerCategory = check; } const QList& MyMoneyTransactionFilter::matchingSplits() const { return m_matchingSplits; } bool MyMoneyTransactionFilter::matchText(const MyMoneySplit * const sp) const { // check if the text is contained in one of the fields // memo, value, number, payee, tag, account if (m_filterSet.singleFilter.textFilter) { MyMoneyFile* file = MyMoneyFile::instance(); const MyMoneyAccount& acc = file->account(sp->accountId()); const MyMoneySecurity& sec = file->security(acc.currencyId()); if (sp->memo().contains(m_text) || sp->shares().formatMoney(acc.fraction(sec)).contains(m_text) || sp->value().formatMoney(acc.fraction(sec)).contains(m_text) || sp->number().contains(m_text) || (m_text.pattern() == sp->transactionId())) return !m_invertText; if (acc.name().contains(m_text)) return !m_invertText; if (!sp->payeeId().isEmpty()) { const MyMoneyPayee& payee = file->payee(sp->payeeId()); if (payee.name().contains(m_text)) return !m_invertText; } if (!sp->tagIdList().isEmpty()) { QList::ConstIterator it_s; QList t = sp->tagIdList(); for (it_s = t.constBegin(); it_s != t.constEnd(); ++it_s) { const MyMoneyTag& tag = file->tag((*it_s)); if (tag.name().contains(m_text)) return !m_invertText; } } return m_invertText; } return true; } bool MyMoneyTransactionFilter::matchAmount(const MyMoneySplit * const sp) const { if (m_filterSet.singleFilter.amountFilter) { if (((sp->value().abs() < m_fromAmount) || sp->value().abs() > m_toAmount) && ((sp->shares().abs() < m_fromAmount) || sp->shares().abs() > m_toAmount)) return false; } return true; } bool MyMoneyTransactionFilter::match(const MyMoneySplit * const sp) const { return matchText(sp) && matchAmount(sp); } bool MyMoneyTransactionFilter::match(const MyMoneyTransaction& transaction) { MyMoneyFile* file = MyMoneyFile::instance(); m_matchingSplits.clear(); // 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 (!m_filterSet.allFilter) { if (m_reportAllSplits) { m_matchingSplits = transaction.splits(); } return true; } // perform checks on the MyMoneyTransaction object first // check the date range if (m_filterSet.singleFilter.dateFilter) { if (m_fromDate != QDate()) { if (transaction.postDate() < m_fromDate) return false; } if (m_toDate != QDate()) { if (transaction.postDate() > m_toDate) return false; } } // construct a local list of pointers to all splits and // remove the ones that do not match account and/or categories. QList matchingSplits; //QList matchingSplits; foreach (const MyMoneySplit& s, transaction.splits()) { matchingSplits.append(&s); } bool categoryMatched = !m_filterSet.singleFilter.categoryFilter; bool accountMatched = !m_filterSet.singleFilter.accountFilter; bool isTransfer = true; // check the transaction's validity if (m_filterSet.singleFilter.validityFilter) { if (m_validity.count() > 0) { if (m_validity.end() == m_validity.find(validTransaction(transaction))) return false; } } QMutableListIterator sp(matchingSplits); if (m_filterSet.singleFilter.accountFilter == 1 || m_filterSet.singleFilter.categoryFilter == 1) { for (sp.toFront(); sp.hasNext();) { bool removeSplit = true; const MyMoneySplit* & s = sp.next(); const MyMoneyAccount& acc = file->account(s->accountId()); if (m_considerCategory) { switch (acc.accountGroup()) { case MyMoneyAccount::Income: case MyMoneyAccount::Expense: isTransfer = false; // check if the split references one of the categories in the list if (m_filterSet.singleFilter.categoryFilter) { if (m_categories.count() > 0) { if (m_categories.end() != m_categories.find(s->accountId())) { categoryMatched = true; removeSplit = false; } } else { // we're looking for transactions with 'no' categories return false; } } break; default: // check if the split references one of the accounts in the list if (m_filterSet.singleFilter.accountFilter) { if (m_accounts.count() > 0) { if (m_accounts.end() != m_accounts.find(s->accountId())) { accountMatched = true; removeSplit = false; } } } else removeSplit = false; break; } } else { if (m_filterSet.singleFilter.accountFilter) { if (m_accounts.count() > 0) { if (m_accounts.end() != m_accounts.find(s->accountId())) { accountMatched = true; removeSplit = false; } } } else removeSplit = false; } if (removeSplit) { // qDebug(" S: %s", (*it).id().data()); sp.remove(); } } } // check if we're looking for transactions without assigned category if (!categoryMatched && transaction.splitCount() == 1 && m_categories.count() == 0) { 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 && !m_filterSet.singleFilter.categoryFilter) categoryMatched = isTransfer; if (matchingSplits.count() == 0 || !(accountMatched && categoryMatched)) return false; FilterSet filterSet = m_filterSet; filterSet.singleFilter.dateFilter = filterSet.singleFilter.accountFilter = filterSet.singleFilter.categoryFilter = 0; // check if we still have something to do if (filterSet.allFilter != 0) { for (sp.toFront(); sp.hasNext();) { bool removeSplit = true; const MyMoneySplit* & s = sp.next(); removeSplit = !(matchAmount(s) && matchText(s)); const MyMoneyAccount& acc = file->account(s->accountId()); // Determine if this account is a category or an account bool isCategory = false; switch (acc.accountGroup()) { case MyMoneyAccount::Income: case MyMoneyAccount::Expense: isCategory = true; default: break; } - if (!isCategory && !removeSplit) { + bool includeSplit = m_considerCategorySplits || (!m_considerCategorySplits && !isCategory); + if (includeSplit && !removeSplit) { // check the payee list if (!removeSplit && m_filterSet.singleFilter.payeeFilter) { if (m_payees.count() > 0) { if (s->payeeId().isEmpty() || m_payees.end() == m_payees.find(s->payeeId())) removeSplit = true; } else if (!s->payeeId().isEmpty()) removeSplit = true; } // check the tag list if (!removeSplit && m_filterSet.singleFilter.tagFilter) { if (m_tags.count() > 0) { if (s->tagIdList().isEmpty()) removeSplit = true; else { bool found = false; for (int i = 0; i < s->tagIdList().size(); i++) { if (m_tags.end() != m_tags.find(s->tagIdList()[i])) { found = true; break; } } if (!found) { removeSplit = true; } } } else if (!s->tagIdList().isEmpty()) removeSplit = true; } // check the type list if (!removeSplit && m_filterSet.singleFilter.typeFilter) { if (m_types.count() > 0) { if (m_types.end() == m_types.find(splitType(transaction, *s))) removeSplit = true; } } // check the state list if (!removeSplit && m_filterSet.singleFilter.stateFilter) { if (m_states.count() > 0) { if (m_states.end() == m_states.find(splitState(*s))) removeSplit = true; } } if (!removeSplit && m_filterSet.singleFilter.nrFilter) { if (!m_fromNr.isEmpty()) { if (s->number() < m_fromNr) removeSplit = true; } if (!m_toNr.isEmpty()) { if (s->number() > m_toNr) removeSplit = true; } } } else if (m_filterSet.singleFilter.payeeFilter || m_filterSet.singleFilter.tagFilter || m_filterSet.singleFilter.typeFilter || m_filterSet.singleFilter.stateFilter || m_filterSet.singleFilter.nrFilter) removeSplit = true; if (removeSplit) { // qDebug(" S: %s", (*it).id().data()); sp.remove(); } } } if (m_reportAllSplits == false && matchingSplits.count() != 0) { m_matchingSplits.append(transaction.splits()[0]); } else { foreach (const MyMoneySplit* s, matchingSplits) { m_matchingSplits.append(*s); } } // all filters passed, I guess we have a match // qDebug(" C: %d", m_matchingSplits.count()); return matchingSplits.count() != 0; } int MyMoneyTransactionFilter::splitState(const MyMoneySplit& split) const { int rc = notReconciled; switch (split.reconcileFlag()) { default: case MyMoneySplit::NotReconciled: break;; case MyMoneySplit::Cleared: rc = cleared; break; case MyMoneySplit::Reconciled: rc = reconciled; break; case MyMoneySplit::Frozen: rc = frozen; break; } return rc; } int MyMoneyTransactionFilter::splitType(const MyMoneyTransaction& t, const MyMoneySplit& split) const { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyAccount a, b; a = file->account(split.accountId()); if ((a.accountGroup() == MyMoneyAccount::Income || a.accountGroup() == MyMoneyAccount::Expense)) return allTypes; if (t.splitCount() == 2) { QString ida, idb; if (t.splits().size() > 0) ida = t.splits()[0].accountId(); if (t.splits().size() > 1) idb = t.splits()[1].accountId(); a = file->account(ida); b = file->account(idb); if ((a.accountGroup() != MyMoneyAccount::Expense && a.accountGroup() != MyMoneyAccount::Income) && (b.accountGroup() != MyMoneyAccount::Expense && b.accountGroup() != MyMoneyAccount::Income)) return transfers; } if (split.value().isPositive()) return deposits; return payments; } MyMoneyTransactionFilter::validityOptionE MyMoneyTransactionFilter::validTransaction(const MyMoneyTransaction& t) const { QList::ConstIterator it_s; MyMoneyMoney val; for (it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) { val += (*it_s).value(); } return (val == MyMoneyMoney()) ? valid : invalid; } bool MyMoneyTransactionFilter::includesCategory(const QString& cat) const { return (! m_filterSet.singleFilter.categoryFilter) || m_categories.end() != m_categories.find(cat); } bool MyMoneyTransactionFilter::includesAccount(const QString& acc) const { return (! m_filterSet.singleFilter.accountFilter) || m_accounts.end() != m_accounts.find(acc); } bool MyMoneyTransactionFilter::includesPayee(const QString& pye) const { return (! m_filterSet.singleFilter.payeeFilter) || m_payees.end() != m_payees.find(pye); } bool MyMoneyTransactionFilter::includesTag(const QString& tag) const { return (! m_filterSet.singleFilter.tagFilter) || m_tags.end() != m_tags.find(tag); } bool MyMoneyTransactionFilter::dateFilter(QDate& from, QDate& to) const { from = m_fromDate; to = m_toDate; return m_filterSet.singleFilter.dateFilter == 1; } bool MyMoneyTransactionFilter::amountFilter(MyMoneyMoney& from, MyMoneyMoney& to) const { from = m_fromAmount; to = m_toAmount; return m_filterSet.singleFilter.amountFilter == 1; } bool MyMoneyTransactionFilter::numberFilter(QString& from, QString& to) const { from = m_fromNr; to = m_toNr; return m_filterSet.singleFilter.nrFilter == 1; } bool MyMoneyTransactionFilter::payees(QStringList& list) const { bool result = m_filterSet.singleFilter.payeeFilter; if (result) { QHashIterator it_payee(m_payees); while (it_payee.hasNext()) { it_payee.next(); list += it_payee.key(); } } return result; } bool MyMoneyTransactionFilter::tags(QStringList& list) const { bool result = m_filterSet.singleFilter.tagFilter; if (result) { QHashIterator it_tag(m_tags); while (it_tag.hasNext()) { it_tag.next(); list += it_tag.key(); } } return result; } bool MyMoneyTransactionFilter::accounts(QStringList& list) const { bool result = m_filterSet.singleFilter.accountFilter; if (result) { QHashIterator it_account(m_accounts); while (it_account.hasNext()) { it_account.next(); QString account = it_account.key(); list += account; } } return result; } bool MyMoneyTransactionFilter::categories(QStringList& list) const { bool result = m_filterSet.singleFilter.categoryFilter; if (result) { QHashIterator it_category(m_categories); while (it_category.hasNext()) { it_category.next(); list += it_category.key(); } } return result; } bool MyMoneyTransactionFilter::types(QList& list) const { bool result = m_filterSet.singleFilter.typeFilter; if (result) { QHashIterator it_type(m_types); while (it_type.hasNext()) { it_type.next(); list += it_type.key(); } } return result; } bool MyMoneyTransactionFilter::states(QList& list) const { bool result = m_filterSet.singleFilter.stateFilter; if (result) { QHashIterator it_state(m_states); while (it_state.hasNext()) { it_state.next(); list += it_state.key(); } } return result; } bool MyMoneyTransactionFilter::firstType(int&i) const { bool result = m_filterSet.singleFilter.typeFilter; if (result) { QHashIterator it_type(m_types); if (it_type.hasNext()) { it_type.next(); i = it_type.key(); } } return result; } bool MyMoneyTransactionFilter::firstState(int&i) const { bool result = m_filterSet.singleFilter.stateFilter; if (result) { QHashIterator it_state(m_states); if (it_state.hasNext()) { it_state.next(); i = it_state.key(); } } return result; } bool MyMoneyTransactionFilter::firstValidity(int&i) const { bool result = m_filterSet.singleFilter.validityFilter; if (result) { QHashIterator it_validity(m_validity); if (it_validity.hasNext()) { it_validity.next(); i = it_validity.key(); } } return result; } bool MyMoneyTransactionFilter::validities(QList& list) const { bool result = m_filterSet.singleFilter.validityFilter; if (result) { QHashIterator it_validity(m_validity); while (it_validity.hasNext()) { it_validity.next(); list += it_validity.key(); } } return result; } bool MyMoneyTransactionFilter::textFilter(QRegExp& exp) const { exp = m_text; return m_filterSet.singleFilter.textFilter == 1; } void MyMoneyTransactionFilter::setDateFilter(dateOptionE 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(dateOptionE id, QDate& start, QDate& end) { bool rc = true; int yr = QDate::currentDate().year(); int mon = QDate::currentDate().month(); switch (id) { case MyMoneyTransactionFilter::allDates: start = QDate(); end = QDate(); break; case MyMoneyTransactionFilter::asOfToday: start = QDate(); end = QDate::currentDate(); break; case MyMoneyTransactionFilter::currentMonth: start = QDate(yr, mon, 1); end = QDate(yr, mon, 1).addMonths(1).addDays(-1); break; case MyMoneyTransactionFilter::currentYear: start = QDate(yr, 1, 1); end = QDate(yr, 12, 31); break; case MyMoneyTransactionFilter::monthToDate: start = QDate(yr, mon, 1); end = QDate::currentDate(); break; case MyMoneyTransactionFilter::yearToDate: start = QDate(yr, 1, 1); end = QDate::currentDate(); break; case MyMoneyTransactionFilter::yearToMonth: start = QDate(yr, 1, 1); end = QDate(yr, mon, 1).addDays(-1); break; case MyMoneyTransactionFilter::lastMonth: start = QDate(yr, mon, 1).addMonths(-1); end = QDate(yr, mon, 1).addDays(-1); break; case MyMoneyTransactionFilter::lastYear: start = QDate(yr, 1, 1).addYears(-1); end = QDate(yr, 12, 31).addYears(-1); break; case MyMoneyTransactionFilter::last7Days: start = QDate::currentDate().addDays(-7); end = QDate::currentDate(); break; case MyMoneyTransactionFilter::last30Days: start = QDate::currentDate().addDays(-30); end = QDate::currentDate(); break; case MyMoneyTransactionFilter::last3Months: start = QDate::currentDate().addMonths(-3); end = QDate::currentDate(); break; case MyMoneyTransactionFilter::last6Months: start = QDate::currentDate().addMonths(-6); end = QDate::currentDate(); break; case MyMoneyTransactionFilter::last11Months: start = QDate(yr, mon, 1).addMonths(-12); end = QDate(yr, mon, 1).addDays(-1); break; case MyMoneyTransactionFilter::last12Months: start = QDate::currentDate().addMonths(-12); end = QDate::currentDate(); break; case MyMoneyTransactionFilter::next7Days: start = QDate::currentDate(); end = QDate::currentDate().addDays(7); break; case MyMoneyTransactionFilter::next30Days: start = QDate::currentDate(); end = QDate::currentDate().addDays(30); break; case MyMoneyTransactionFilter::next3Months: start = QDate::currentDate(); end = QDate::currentDate().addMonths(3); break; case MyMoneyTransactionFilter::next6Months: start = QDate::currentDate(); end = QDate::currentDate().addMonths(6); break; case MyMoneyTransactionFilter::next12Months: start = QDate::currentDate(); end = QDate::currentDate().addMonths(12); break; case MyMoneyTransactionFilter::next18Months: start = QDate::currentDate(); end = QDate::currentDate().addMonths(18); break; case MyMoneyTransactionFilter::userDefined: start = QDate(); end = QDate(); break; case MyMoneyTransactionFilter::last3ToNext3Months: start = QDate::currentDate().addMonths(-3); end = QDate::currentDate().addMonths(3); break; case MyMoneyTransactionFilter::currentQuarter: start = QDate(yr, mon - ((mon - 1) % 3), 1); end = start.addMonths(3).addDays(-1); break; case MyMoneyTransactionFilter::lastQuarter: start = QDate(yr, mon - ((mon - 1) % 3), 1).addMonths(-3); end = start.addMonths(3).addDays(-1); break; case MyMoneyTransactionFilter::nextQuarter: start = QDate(yr, mon - ((mon - 1) % 3), 1).addMonths(3); end = start.addMonths(3).addDays(-1); break; case MyMoneyTransactionFilter::currentFiscalYear: start = QDate(QDate::currentDate().year(), fiscalYearStartMonth, fiscalYearStartDay); if (QDate::currentDate() < start) start = start.addYears(-1); end = start.addYears(1).addDays(-1); break; case MyMoneyTransactionFilter::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 MyMoneyTransactionFilter::today: start = QDate::currentDate(); end = QDate::currentDate(); break; default: qWarning("Unknown date identifier %d in MyMoneyTransactionFilter::translateDateRange()", id); rc = false; break; } return rc; } void MyMoneyTransactionFilter::removeReference(const QString& id) { if (m_accounts.end() != m_accounts.find(id)) { qDebug("%s", qPrintable(QString("Remove account '%1' from report").arg(id))); m_accounts.take(id); } else if (m_categories.end() != m_categories.find(id)) { qDebug("%s", qPrintable(QString("Remove category '%1' from report").arg(id))); m_categories.remove(id); } else if (m_payees.end() != m_payees.find(id)) { qDebug("%s", qPrintable(QString("Remove payee '%1' from report").arg(id))); m_payees.remove(id); } else if (m_tags.end() != m_tags.find(id)) { qDebug("%s", qPrintable(QString("Remove tag '%1' from report").arg(id))); m_tags.remove(id); } } diff --git a/kmymoney/mymoney/mymoneytransactionfilter.h b/kmymoney/mymoney/mymoneytransactionfilter.h index f394b4a97..594a2fa2a 100644 --- a/kmymoney/mymoney/mymoneytransactionfilter.h +++ b/kmymoney/mymoney/mymoneytransactionfilter.h @@ -1,640 +1,650 @@ /*************************************************************************** mymoneytransactionfilter.h - description ------------------- begin : Fri Aug 22 2003 copyright : (C) 2000-2003 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef MYMONEYTRANSACTIONFILTER_H #define MYMONEYTRANSACTIONFILTER_H // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneytransaction.h" #include "kmm_mymoney_export.h" /** * @author Thomas Baumgart */ class KMM_MYMONEY_EXPORT MyMoneyTransactionFilter { public: // Make sure to keep the following enum valus in sync with the values // used by the GUI (for KMyMoney in kfindtransactiondlgdecl.ui) enum typeOptionE { allTypes = 0, payments, deposits, transfers, // insert new constants above of this line typeOptionCount }; // Make sure to keep the following enum valus in sync with the values // used by the GUI (for KMyMoney in kfindtransactiondlgdecl.ui) enum stateOptionE { allStates = 0, notReconciled, cleared, reconciled, frozen, // insert new constants above of this line stateOptionCount }; // Make sure to keep the following enum valus in sync with the values // used by the GUI (for KMyMoney in kfindtransactiondlgdecl.ui) enum validityOptionE { anyValidity = 0, valid, invalid, // insert new constants above of this line validityOptionCount }; // Make sure to keep the following enum valus in sync with the values // used by the GUI (for KMyMoney in kfindtransactiondlgdecl.ui) enum dateOptionE { allDates = 0, asOfToday, currentMonth, currentYear, monthToDate, yearToDate, yearToMonth, lastMonth, lastYear, last7Days, last30Days, last3Months, last6Months, last12Months, next7Days, next30Days, next3Months, next6Months, next12Months, userDefined, last3ToNext3Months, last11Months, currentQuarter, lastQuarter, nextQuarter, currentFiscalYear, lastFiscalYear, today, next18Months, // insert new constants above of this line dateOptionCount }; 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 */ MyMoneyTransactionFilter(const QString& id); ~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(dateOptionE 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 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 * const sp) 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 * * @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 * const sp) const; /** * Convenience method which actually returns matchText(sp) && matchAmount(sp). */ bool match(const MyMoneySplit * const sp) 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); + /** Consider splits in categories + * + * With this setting, splits in categories that are not considered + * by default are taken into account. + * + * @param check check state + */ + void setConsiderCategorySplits(const bool check = true); + /** * Consider income and expense categories * * If the account or category filter is enabled, categories of * income and expense type are included if enabled with this * method. * * @param check check state */ void setConsiderCategory(const bool check = true); /** * 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. */ const QList& matchingSplits() const; /** * 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 */ const QDate fromDate() const { return m_fromDate; }; /** * 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 */ const QDate toDate() const { return m_toDate; }; /** * 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 validity filter has been set, * and if so, it returns the first validity in the filter. * * @param i reference to int to replace with first validity filter, untouched otherwise * @return return true if a validity filter has been set */ bool firstValidity(int& i) const; bool validities(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 { return m_invertText; }; /** * 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(dateOptionE range, QDate& start, QDate& end); static void setFiscalYearStart(int firstMonth, int firstDay); FilterSet filterSet() const { return m_filterSet; }; /** * 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 MyMoneySplit::reconcileFlagE * 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; /** * 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 */ validityOptionE validTransaction(const MyMoneyTransaction& transaction) const; protected: FilterSet m_filterSet; bool m_reportAllSplits; bool m_considerCategory; + bool m_considerCategorySplits; 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; QList m_matchingSplits; }; /** * Make it possible to hold @ref MyMoneyTransactionFilter objects inside @ref QVariant objects. */ Q_DECLARE_METATYPE(MyMoneyTransactionFilter) #endif diff --git a/kmymoney/mymoney/mymoneytransactionfiltertest.cpp b/kmymoney/mymoney/mymoneytransactionfiltertest.cpp index 65b8ce715..da20ddfea 100644 --- a/kmymoney/mymoney/mymoneytransactionfiltertest.cpp +++ b/kmymoney/mymoney/mymoneytransactionfiltertest.cpp @@ -1,814 +1,817 @@ /*************************************************************************** mymoneytransactionfiltertest.cpp ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "mymoneytransactionfiltertest.h" #include #include "mymoneytransactionfilter.h" #include "mymoneyfile.h" #include "mymoneyseqaccessmgr.h" #undef QVERIFY #define QVERIFY(statement) \ QTest::qVerify((statement), #statement, "", __FILE__, __LINE__) #undef QCOMPARE #define QCOMPARE(actual, expected) \ QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__) using namespace std; // copied from reports/reportstestcommon.cpp QString makeAccount(const QString& _name, MyMoneyAccount::accountTypeE _type, MyMoneyMoney _balance, const QDate& _open, const QString& _parent, QString _currency = "", bool _taxReport = false, bool _openingBalance = false); QString makeAccount(const QString& _name, MyMoneyAccount::accountTypeE _type, MyMoneyMoney _balance, const QDate& _open, const QString& _parent, QString _currency, bool _taxReport, bool _openingBalance) { MyMoneyAccount info; MyMoneyFileTransaction ft; info.setName(_name); info.setAccountType(_type); info.setOpeningDate(_open); if (!_currency.isEmpty()) info.setCurrencyId(_currency); else info.setCurrencyId(MyMoneyFile::instance()->baseCurrency().id()); if (_taxReport) info.setValue("Tax", "Yes"); if (_openingBalance) info.setValue("OpeningBalanceAccount", "Yes"); MyMoneyAccount parent = MyMoneyFile::instance()->account(_parent); MyMoneyFile::instance()->addAccount(info, parent); // create the opening balance transaction if any if (!_balance.isZero()) { MyMoneySecurity sec = MyMoneyFile::instance()->currency(info.currencyId()); MyMoneyFile::instance()->openingBalanceAccount(sec); MyMoneyFile::instance()->createOpeningBalanceTransaction(info, _balance); } ft.commit(); return info.id(); } void MyMoneyTransactionFilterTest::initTestCase() { MyMoneySeqAccessMgr *storage = new MyMoneySeqAccessMgr; MyMoneyFile *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", MyMoneyAccount::Checkings, MyMoneyMoney(0.0), QDate(2004, 1, 1), acAsset); acExpenseId = makeAccount("Expense", MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); acIncomeId = makeAccount("Expense", MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acIncome); ft.commit(); } void MyMoneyTransactionFilterTest::testMatchAmount() { MyMoneySplit split; split.setShares(MyMoneyMoney(123.20)); MyMoneyTransactionFilter filter; QVERIFY (filter.matchAmount(&split)); filter.setAmountFilter(MyMoneyMoney("123.0"), MyMoneyMoney("124.0")); QVERIFY (filter.matchAmount(&split)); filter.setAmountFilter(MyMoneyMoney("120.0"), MyMoneyMoney("123.0")); QVERIFY (!filter.matchAmount(&split)); } void MyMoneyTransactionFilterTest::testMatchText() { MyMoneySplit split; MyMoneyTransactionFilter filter; // no filter QVERIFY (filter.matchText(&split)); 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"); QVERIFY (filter.matchText(&split)); QVERIFY (!filterInvert.matchText(&split)); QVERIFY (!filterNotFound.matchText(&split)); split.setMemo(""); // payee split.setPayeeId(payeeId); QVERIFY (filter.matchText(&split)); QVERIFY (!filterInvert.matchText(&split)); QVERIFY (!filterNotFound.matchText(&split)); split.setPayeeId(""); // tag split.setTagIdList(tagIdList); QVERIFY (filter.matchText(&split)); QVERIFY (!filterInvert.matchText(&split)); QVERIFY (!filterNotFound.matchText(&split)); split.setTagIdList(QStringList()); // value split.setValue(MyMoneyMoney("10.2")); QVERIFY (filter.matchText(&split)); QVERIFY (!filterInvert.matchText(&split)); QVERIFY (!filterNotFound.matchText(&split)); split.setValue(MyMoneyMoney("0.0")); // number split.setNumber("10.2"); QVERIFY (filter.matchText(&split)); QVERIFY (!filterInvert.matchText(&split)); QVERIFY (!filterNotFound.matchText(&split)); split.setNumber("0.0"); // transaction id split.setTransactionId("10.2"); QVERIFY (filter.matchText(&split)); QVERIFY (!filterInvert.matchText(&split)); QVERIFY (!filterNotFound.matchText(&split)); split.setTransactionId("0.0"); // account split.setAccountId(acCheckingId); QVERIFY (filter.matchText(&split)); QVERIFY (!filterInvert.matchText(&split)); QVERIFY (!filterNotFound.matchText(&split)); } 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().size(), 2); filter.setReportAllSplits(false); QVERIFY(filter.match(transaction)); // FIXME: is it correct to return no splits ? QCOMPARE(filter.matchingSplits().size(), 0); } 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().size(), 1); filter.setReportAllSplits(false); filter.setConsiderCategory(false); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().size(), 1); filter.setReportAllSplits(false); filter.setConsiderCategory(true); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().size(), 1); filter.setReportAllSplits(true); filter.setConsiderCategory(true); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().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().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().size(), 2); // check same date filter.setDateFilter(QDate(2014, 1, 2), QDate(2014, 1, 2)); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().size(), 2); filter.setReportAllSplits(false); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().size(), 1); // check bad date order filter.setDateFilter(QDate(2014, 1, 2), QDate(2014, 1, 1)); QVERIFY(!filter.match(transaction)); 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().size(), 4); filter.setNumberFilter("1", ""); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().size(), 4); filter.setNumberFilter("", "4"); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().size(), 4); filter.setNumberFilter("1", "4"); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().size(), 4); filter.setNumberFilter("1", "2"); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().size(), 2); // do not return all matching splits filter.setReportAllSplits(false); filter.setNumberFilter("1", "4"); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().size(), 1); filter.setNumberFilter("1", "2"); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().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().size(), 1); filter.setReportAllSplits(false); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().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().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(MyMoneySplit::NotReconciled); MyMoneySplit split2; split2.setAccountId(acCheckingId); split2.setShares(MyMoneyMoney(1.00)); split2.setReconcileFlag(MyMoneySplit::Cleared); MyMoneySplit split3; split3.setAccountId(acCheckingId); split3.setShares(MyMoneyMoney(100.00)); split3.setReconcileFlag(MyMoneySplit::Reconciled); MyMoneySplit split4; split4.setAccountId(acCheckingId); split4.setShares(MyMoneyMoney(22.00)); split4.setReconcileFlag(MyMoneySplit::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().size(), 4); // all states filter.addState(MyMoneyTransactionFilter::notReconciled); filter.addState(MyMoneyTransactionFilter::cleared); filter.addState(MyMoneyTransactionFilter::reconciled); filter.addState(MyMoneyTransactionFilter::frozen); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().size(), 4); // single state filter.clear(); filter.addState(MyMoneyTransactionFilter::notReconciled); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().size(), 1); filter.clear(); filter.addState(MyMoneyTransactionFilter::cleared); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().size(), 1); filter.clear(); filter.addState(MyMoneyTransactionFilter::reconciled); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().size(), 1); filter.clear(); filter.addState(MyMoneyTransactionFilter::frozen); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().size(), 1); // check no category support MyMoneySplit split5; split5.setAccountId(acCheckingId); split5.setShares(MyMoneyMoney(22.00)); split5.setReconcileFlag(MyMoneySplit::Frozen); MyMoneyTransaction transaction2; transaction.setPostDate(QDate(2014, 1, 2)); transaction.addSplit(split5); filter.clear(); filter.setReportAllSplits(true); filter.addState(MyMoneyTransactionFilter::frozen); QVERIFY(!filter.match(transaction2)); QCOMPARE(filter.matchingSplits().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().size(), 2); filter.setReportAllSplits(false); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().size(), 1); - // check no category support + // check disabled category splits 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().size(), 0); - qDebug() << "tags on categories could not be tested"; + // check enabled category splits support + filter.setConsiderCategorySplits(true); + QVERIFY(filter.match(transaction2)); + QCOMPARE(filter.matchingSplits().size(), 1); } 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().size(), 2); filter.addType(MyMoneyTransactionFilter::allTypes); qDebug() << "MyMoneyTransactionFilter::allTypes could not be tested"; qDebug() << "because type filter does not work with categories"; QVERIFY(!filter.match(transaction)); QCOMPARE(filter.matchingSplits().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().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().size(), 1); // deposits filter.addType(MyMoneyTransactionFilter::deposits); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().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().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().size(), 1); // valid payments filter.addType(MyMoneyTransactionFilter::payments); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().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().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().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().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().size(), 2); filter.addType(MyMoneyTransactionFilter::transfers); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().size(), 2); // transfers - invalid number of counts transaction.addSplit(split3); QVERIFY(!filter.match(transaction)); QCOMPARE(filter.matchingSplits().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().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(MyMoneyTransactionFilter::valid); filter.setReportAllSplits(true); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().size(), 2); filter.setReportAllSplits(false); QVERIFY(filter.match(transaction)); QCOMPARE(filter.matchingSplits().size(), 1); // check invalid transaction filter.clear(); filter.addValidity(MyMoneyTransactionFilter::invalid); filter.setReportAllSplits(true); QVERIFY(!filter.match(transaction)); QCOMPARE(filter.matchingSplits().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().size(), 3); filter.clear(); filter.addValidity(MyMoneyTransactionFilter::valid); QVERIFY(!filter.match(transaction)); QCOMPARE(filter.matchingSplits().size(), 0); } QTEST_MAIN(MyMoneyTransactionFilterTest) diff --git a/kmymoney/views/ktagsview.cpp b/kmymoney/views/ktagsview.cpp index e14817964..94ff35f36 100644 --- a/kmymoney/views/ktagsview.cpp +++ b/kmymoney/views/ktagsview.cpp @@ -1,673 +1,674 @@ /*************************************************************************** ktagsview.h ------------- begin : Sat Oct 13 2012 copyright : (C) 2012 by Alessandro Russo ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "ktagsview.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyfile.h" #include "kmymoneyglobalsettings.h" #include "kmymoney.h" #include "models.h" #include "mymoneysecurity.h" /* -------------------------------------------------------------------------*/ /* KTransactionPtrVector */ /* -------------------------------------------------------------------------*/ // *** KTagListItem Implementation *** KTagListItem::KTagListItem(QListWidget *parent, const MyMoneyTag& tag) : QListWidgetItem(parent, QListWidgetItem::UserType), m_tag(tag) { setText(tag.name()); // allow in column rename setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); } KTagListItem::~KTagListItem() { } // *** KTagsView Implementation *** KTagsView::KTagsView(QWidget *parent) : QWidget(parent), m_needReload(false), m_inSelection(false), m_allowEditing(true), m_tagFilterType(0) { setupUi(this); m_filterProxyModel = new AccountNamesFilterProxyModel(this); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Asset); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Liability); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Income); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Expense); m_filterProxyModel->setSourceModel(Models::instance()->accountsModel()); m_filterProxyModel->sort(0); // create the searchline widget // and insert it into the existing layout m_searchWidget = new KListWidgetSearchLine(this, m_tagsList); m_searchWidget->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); m_tagsList->setContextMenuPolicy(Qt::CustomContextMenu); m_listTopHLayout->insertWidget(0, m_searchWidget); //load the filter type m_filterBox->addItem(i18nc("@item Show all tags", "All")); m_filterBox->addItem(i18nc("@item Show only used tags", "Used")); m_filterBox->addItem(i18nc("@item Show only unused tags", "Unused")); m_filterBox->addItem(i18nc("@item Show only opened tags", "Opened")); m_filterBox->addItem(i18nc("@item Show only closed tags", "Closed")); m_filterBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); KGuiItem newButtonItem(QString(""), KIcon("list-add-tag"), i18n("Creates a new tag"), i18n("Use this to create a new tag.")); m_newButton->setGuiItem(newButtonItem); m_newButton->setToolTip(newButtonItem.toolTip()); KGuiItem renameButtonItem(QString(""), KIcon("edit-rename"), i18n("Rename the current selected tag"), i18n("Use this to start renaming the selected tag.")); m_renameButton->setGuiItem(renameButtonItem); m_renameButton->setToolTip(renameButtonItem.toolTip()); KGuiItem deleteButtonItem(QString(""), KIcon("list-remove-tag"), i18n("Delete the current selected tag"), i18n("Use this to delete the selected tag.")); m_deleteButton->setGuiItem(deleteButtonItem); m_deleteButton->setToolTip(deleteButtonItem.toolTip()); KGuiItem updateButtonItem(i18nc("Update tag", "Update"), KIcon("dialog-ok"), i18n("Accepts the entered data and stores it"), i18n("Use this to accept the modified data.")); m_updateButton->setGuiItem(updateButtonItem); m_updateButton->setEnabled(false); QList cols; cols << KMyMoneyRegister::DateColumn; cols << KMyMoneyRegister::AccountColumn; cols << KMyMoneyRegister::DetailColumn; cols << KMyMoneyRegister::ReconcileFlagColumn; cols << KMyMoneyRegister::PaymentColumn; cols << KMyMoneyRegister::DepositColumn; m_register->setupRegister(MyMoneyAccount(), cols); m_register->setSelectionMode(QTableWidget::SingleSelection); m_register->setDetailsColumnType(KMyMoneyRegister::AccountFirst); m_balanceLabel->hide(); connect(m_tagsList, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(slotSelectTag(QListWidgetItem*,QListWidgetItem*))); connect(m_tagsList, SIGNAL(itemSelectionChanged()), this, SLOT(slotSelectTag())); connect(m_tagsList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(slotStartRename(QListWidgetItem*))); connect(m_tagsList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(slotRenameTag(QListWidgetItem*))); connect(m_tagsList, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotOpenContextMenu(QPoint))); connect(m_renameButton, SIGNAL(clicked()), this, SLOT(slotRenameButtonCliked())); connect(m_deleteButton, SIGNAL(clicked()), kmymoney->action("tag_delete"), SLOT(trigger())); connect(m_newButton, SIGNAL(clicked()), this, SLOT(slotTagNew())); connect(m_colorbutton, SIGNAL(changed(QColor)), this, SLOT(slotTagDataChanged())); connect(m_closed, SIGNAL(stateChanged(int)), this, SLOT(slotTagDataChanged())); connect(m_notes, SIGNAL(textChanged()), this, SLOT(slotTagDataChanged())); connect(m_updateButton, SIGNAL(clicked()), this, SLOT(slotUpdateTag())); connect(m_helpButton, SIGNAL(clicked()), this, SLOT(slotHelp())); connect(m_register, SIGNAL(editTransaction()), this, SLOT(slotSelectTransaction())); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadTags())); connect(m_filterBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChangeFilter(int))); // use the size settings of the last run (if any) KConfigGroup grp = KGlobal::config()->group("Last Use Settings"); m_splitter->restoreState(grp.readEntry("KTagsViewSplitterSize", QByteArray())); m_splitter->setChildrenCollapsible(false); // At start we haven't any tag selected m_tabWidget->setEnabled(false); // disable tab widget m_deleteButton->setEnabled(false); // disable delete and rename button m_renameButton->setEnabled(false); m_tag = MyMoneyTag(); // make sure we don't access an undefined tag clearItemData(); } KTagsView::~KTagsView() { // remember the splitter settings for startup KConfigGroup grp = KGlobal::config()->group("Last Use Settings"); grp.writeEntry("KTagsViewSplitterSize", m_splitter->saveState()); grp.sync(); } void KTagsView::slotStartRename(QListWidgetItem* item) { m_allowEditing = true; m_tagsList->editItem(item); } void KTagsView::slotRenameButtonCliked() { if (m_tagsList->currentItem() && m_tagsList->selectedItems().count() == 1) { slotStartRename(m_tagsList->currentItem()); } } // This variant is only called when a single tag is selected and renamed. void KTagsView::slotRenameTag(QListWidgetItem* ta) { //if there is no current item selected, exit if (m_allowEditing == false || !m_tagsList->currentItem() || ta != m_tagsList->currentItem()) return; //kDebug() << "[KTagsView::slotRenameTag]"; // create a copy of the new name without appended whitespaces QString new_name = ta->text(); if (m_tag.name() != new_name) { MyMoneyFileTransaction ft; try { // check if we already have a tag with the new name try { // this function call will throw an exception, if the tag // hasn't been found. MyMoneyFile::instance()->tagByName(new_name); // the name already exists, ask the user whether he's sure to keep the name if (KMessageBox::questionYesNo(this, i18n("A tag with the name '%1' already exists. It is not advisable to have " "multiple tags with the same identification name. Are you sure you would like " "to rename the tag?", new_name)) != KMessageBox::Yes) { ta->setText(m_tag.name()); return; } } catch (const MyMoneyException &) { // all ok, the name is unique } m_tag.setName(new_name); m_newName = new_name; MyMoneyFile::instance()->modifyTag(m_tag); // the above call to modifyTag will reload the view so // all references and pointers to the view have to be // re-established. // make sure, that the record is visible even if it moved // out of sight due to the rename operation ensureTagVisible(m_tag.id()); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(nullptr, i18n("Unable to modify tag"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } else { ta->setText(new_name); } } void KTagsView::ensureTagVisible(const QString& id) { for (int i = 0; i < m_tagsList->count(); ++i) { KTagListItem* ta = dynamic_cast(m_tagsList->item(0)); if (ta && ta->tag().id() == id) { m_tagsList->scrollToItem(ta, QAbstractItemView::PositionAtCenter); m_tagsList->setCurrentItem(ta); // active item and deselect all others m_tagsList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it break; } } } void KTagsView::selectedTags(QList& tagsList) const { QList selectedItems = m_tagsList->selectedItems(); QList::ConstIterator itemsIt = selectedItems.constBegin(); while (itemsIt != selectedItems.constEnd()) { KTagListItem* item = dynamic_cast(*itemsIt); if (item) tagsList << item->tag(); ++itemsIt; } } void KTagsView::slotSelectTag(QListWidgetItem* cur, QListWidgetItem* prev) { Q_UNUSED(cur); Q_UNUSED(prev); m_allowEditing = false; } void KTagsView::slotSelectTag() { // check if the content of a currently selected tag was modified // and ask to store the data if (m_updateButton->isEnabled()) { if (KMessageBox::questionYesNo(this, QString("%1").arg( i18n("Do you want to save the changes for %1?", m_newName)), i18n("Save changes")) == KMessageBox::Yes) { m_inSelection = true; slotUpdateTag(); m_inSelection = false; } } // loop over all tags and count the number of tags, also // obtain last selected tag QList tagsList; selectedTags(tagsList); emit selectObjects(tagsList); if (tagsList.isEmpty()) { m_tabWidget->setEnabled(false); // disable tab widget m_balanceLabel->hide(); m_deleteButton->setEnabled(false); //disable delete and rename button m_renameButton->setEnabled(false); clearItemData(); m_tag = MyMoneyTag(); return; // make sure we don't access an undefined tag } m_deleteButton->setEnabled(true); //re-enable delete button // if we have multiple tags selected, clear and disable the tag information if (tagsList.count() > 1) { m_tabWidget->setEnabled(false); // disable tab widget m_renameButton->setEnabled(false); // disable also the rename button m_balanceLabel->hide(); clearItemData(); } else m_renameButton->setEnabled(true); // otherwise we have just one selected, enable tag information widget and renameButton m_tabWidget->setEnabled(true); m_balanceLabel->show(); // as of now we are updating only the last selected tag, and until // selection mode of the QListView has been changed to Extended, this // will also be the only selection and behave exactly as before - Andreas try { m_tag = tagsList[0]; m_newName = m_tag.name(); m_colorbutton->setEnabled(true); m_colorbutton->setColor(m_tag.tagColor()); m_closed->setEnabled(true); m_closed->setChecked(m_tag.isClosed()); m_notes->setEnabled(true); m_notes->setText(m_tag.notes()); slotTagDataChanged(); showTransactions(); } catch (const MyMoneyException &e) { qDebug("exception during display of tag: %s at %s:%ld", qPrintable(e.what()), qPrintable(e.file()), e.line()); m_register->clear(); m_tag = MyMoneyTag(); } m_allowEditing = true; } void KTagsView::clearItemData() { m_colorbutton->setColor(QColor()); m_closed->setChecked(false); m_notes->setText(QString()); showTransactions(); } void KTagsView::showTransactions() { MyMoneyMoney balance; MyMoneyFile *file = MyMoneyFile::instance(); MyMoneySecurity base = file->baseCurrency(); // setup sort order m_register->setSortOrder(KMyMoneyGlobalSettings::sortSearchView()); // clear the register m_register->clear(); if (m_tag.id().isEmpty() || !m_tabWidget->isEnabled()) { m_balanceLabel->setText(i18n("Balance: %1", balance.formatMoney(file->baseCurrency().smallestAccountFraction()))); return; } // setup the list and the pointer vector MyMoneyTransactionFilter filter; + filter.setConsiderCategorySplits(); filter.addTag(m_tag.id()); filter.setDateFilter(KMyMoneyGlobalSettings::startDate().date(), QDate()); // retrieve the list from the engine file->transactionList(m_transactionList, filter); // create the elements for the register QList >::const_iterator it; QMap uniqueMap; MyMoneyMoney deposit, payment; int splitCount = 0; bool balanceAccurate = true; for (it = m_transactionList.constBegin(); it != m_transactionList.constEnd(); ++it) { const MyMoneySplit& split = (*it).second; MyMoneyAccount acc = file->account(split.accountId()); ++splitCount; uniqueMap[(*it).first.id()]++; KMyMoneyRegister::Register::transactionFactory(m_register, (*it).first, (*it).second, uniqueMap[(*it).first.id()]); // take care of foreign currencies MyMoneyMoney val = split.shares().abs(); if (acc.currencyId() != base.id()) { const MyMoneyPrice &price = file->price(acc.currencyId(), base.id()); // in case the price is valid, we use it. Otherwise, we keep // a flag that tells us that the balance is somewhat inaccurate if (price.isValid()) { val *= price.rate(base.id()); } else { balanceAccurate = false; } } if (split.shares().isNegative()) { payment += val; } else { deposit += val; } } balance = deposit - payment; // add the group markers m_register->addGroupMarkers(); // sort the transactions according to the sort setting m_register->sortItems(); // remove trailing and adjacent markers m_register->removeUnwantedGroupMarkers(); m_register->updateRegister(true); // we might end up here with updates disabled on the register so // make sure that we enable updates here m_register->setUpdatesEnabled(true); m_balanceLabel->setText(i18n("Balance: %1%2", balanceAccurate ? "" : "~", balance.formatMoney(file->baseCurrency().smallestAccountFraction()))); } void KTagsView::slotTagDataChanged() { bool rc = false; if (m_tabWidget->isEnabled()) { rc |= ((m_tag.tagColor().isValid() != m_colorbutton->color().isValid()) || (m_colorbutton->color().isValid() && m_tag.tagColor() != m_colorbutton->color())); rc |= (m_closed->isChecked() != m_tag.isClosed()); rc |= ((m_tag.notes().isEmpty() != m_notes->toPlainText().isEmpty()) || (!m_notes->toPlainText().isEmpty() && m_tag.notes() != m_notes->toPlainText())); } m_updateButton->setEnabled(rc); } void KTagsView::slotUpdateTag() { if (m_updateButton->isEnabled()) { MyMoneyFileTransaction ft; m_updateButton->setEnabled(false); try { m_tag.setName(m_newName); m_tag.setTagColor(m_colorbutton->color()); m_tag.setClosed(m_closed->isChecked()); m_tag.setNotes(m_notes->toPlainText()); MyMoneyFile::instance()->modifyTag(m_tag); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(nullptr, i18n("Unable to modify tag"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } } void KTagsView::showEvent(QShowEvent* event) { emit aboutToShow(); if (m_needReload) { loadTags(); m_needReload = false; } // don't forget base class implementation QWidget::showEvent(event); QList list; selectedTags(list); emit selectObjects(list); } void KTagsView::slotLoadTags() { if (isVisible()) { if (m_inSelection) QTimer::singleShot(0, this, SLOT(slotLoadTags())); else loadTags(); } else { m_needReload = true; } } void KTagsView::loadTags() { if (m_inSelection) return; QMap isSelected; QString id; MyMoneyFile* file = MyMoneyFile::instance(); // remember which items are selected in the list QList selectedItems = m_tagsList->selectedItems(); QList::const_iterator tagsIt = selectedItems.constBegin(); while (tagsIt != selectedItems.constEnd()) { KTagListItem* item = dynamic_cast(*tagsIt); if (item) isSelected[item->tag().id()] = true; ++tagsIt; } // keep current selected item KTagListItem *currentItem = static_cast(m_tagsList->currentItem()); if (currentItem) id = currentItem->tag().id(); m_allowEditing = false; // clear the list m_searchWidget->clear(); m_searchWidget->updateSearch(); m_tagsList->clear(); m_register->clear(); currentItem = 0; QListlist = file->tagList(); QList::ConstIterator it; for (it = list.constBegin(); it != list.constEnd(); ++it) { if (m_tagFilterType == eAllTags || (m_tagFilterType == eReferencedTags && file->isReferenced(*it)) || (m_tagFilterType == eUnusedTags && !file->isReferenced(*it)) || (m_tagFilterType == eOpenedTags && !(*it).isClosed()) || (m_tagFilterType == eClosedTags && (*it).isClosed())) { KTagListItem* item = new KTagListItem(m_tagsList, *it); if (item->tag().id() == id) currentItem = item; if (isSelected[item->tag().id()]) item->setSelected(true); } } m_tagsList->sortItems(); if (currentItem) { m_tagsList->setCurrentItem(currentItem); m_tagsList->scrollToItem(currentItem); } m_filterProxyModel->invalidate(); slotSelectTag(0, 0); m_allowEditing = true; } void KTagsView::slotSelectTransaction() { QList list = m_register->selectedItems(); if (!list.isEmpty()) { KMyMoneyRegister::Transaction* t = dynamic_cast(list[0]); if (t) emit transactionSelected(t->split().accountId(), t->transaction().id()); } } void KTagsView::slotSelectTagAndTransaction(const QString& tagId, const QString& accountId, const QString& transactionId) { if (!isVisible()) return; try { // clear filter m_searchWidget->clear(); m_searchWidget->updateSearch(); // deselect all other selected items QList selectedItems = m_tagsList->selectedItems(); QList::const_iterator tagsIt = selectedItems.constBegin(); while (tagsIt != selectedItems.constEnd()) { KTagListItem* item = dynamic_cast(*tagsIt); if (item) item->setSelected(false); ++tagsIt; } // find the tag in the list QListWidgetItem* it; for (int i = 0; i < m_tagsList->count(); ++i) { it = m_tagsList->item(i); KTagListItem* item = dynamic_cast(it); if (item && item->tag().id() == tagId) { m_tagsList->scrollToItem(it, QAbstractItemView::PositionAtCenter); m_tagsList->setCurrentItem(it); // active item and deselect all others m_tagsList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it //make sure the tag selection is updated and transactions are updated accordingly slotSelectTag(); KMyMoneyRegister::RegisterItem *item = 0; for (int i = 0; i < m_register->rowCount(); ++i) { item = m_register->itemAtRow(i); KMyMoneyRegister::Transaction* t = dynamic_cast(item); if (t) { if (t->transaction().id() == transactionId && t->transaction().accountReferenced(accountId)) { m_register->selectItem(item); m_register->ensureItemVisible(item); break; } } } // quit out of for() loop break; } } } catch (const MyMoneyException &e) { qWarning("Unexpected exception in KTagsView::slotSelectTagAndTransaction %s", qPrintable(e.what())); } } void KTagsView::slotOpenContextMenu(const QPoint& /*ta*/) { KTagListItem* item = dynamic_cast(m_tagsList->currentItem()); if (item) { slotSelectTag(); emit openContextMenu(item->tag()); } } void KTagsView::slotTagNew() { kmymoney->action("tag_new")->trigger(); } void KTagsView::slotHelp() { KToolInvocation::invokeHelp("details.tags.attributes"); //FIXME-ALEX update help file } void KTagsView::slotChangeFilter(int index) { //update the filter type then reload the tags list m_tagFilterType = index; loadTags(); }