File indexing completed on 2024-05-19 05:08:36

0001 /*
0002     SPDX-FileCopyrightText: 2002-2019 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "ktransactionfilter.h"
0008 #include "ktransactionfilter_p.h"
0009 
0010 // ----------------------------------------------------------------------------
0011 // QT Includes
0012 
0013 // ----------------------------------------------------------------------------
0014 // KDE Includes
0015 
0016 #include <KHelpClient>
0017 #include <KLocalizedString>
0018 
0019 // ----------------------------------------------------------------------------
0020 // Project Includes
0021 
0022 #include "daterangedlg.h"
0023 #include "kmymoneysettings.h"
0024 #include "mymoneyexception.h"
0025 #include "mymoneyfile.h"
0026 #include "mymoneyreport.h"
0027 #include "mymoneysplit.h"
0028 #include "mymoneytransaction.h"
0029 #include "mymoneytransactionfilter.h"
0030 #include "mymoneyutils.h"
0031 
0032 #include "ui_ktransactionfilter.h"
0033 
0034 KTransactionFilter::KTransactionFilter(QWidget *parent, bool withEquityAccounts, bool withInvestments, bool withDataTab) :
0035     QWidget(parent),
0036     d_ptr(new KTransactionFilterPrivate(this))
0037 {
0038     Q_D(KTransactionFilter);
0039     d->init(withEquityAccounts, withInvestments, withDataTab);
0040     QMetaObject::invokeMethod(d->ui->m_textEdit, "setFocus", Qt::QueuedConnection);
0041 }
0042 
0043 KTransactionFilter::~KTransactionFilter()
0044 {
0045     Q_D(KTransactionFilter);
0046     delete d;
0047 }
0048 
0049 void KTransactionFilter::slotReset()
0050 {
0051     Q_D(KTransactionFilter);
0052     d->ui->m_textEdit->setText(QString());
0053     d->ui->m_regExp->setChecked(false);
0054     d->ui->m_caseSensitive->setChecked(false);
0055     d->ui->m_textNegate->setCurrentItem(0);
0056 
0057     d->ui->m_amountEdit->setEnabled(true);
0058     d->ui->m_amountFromEdit->setEnabled(false);
0059     d->ui->m_amountToEdit->setEnabled(false);
0060     d->ui->m_amountEdit->setText(QString());
0061     d->ui->m_amountFromEdit->setText(QString());
0062     d->ui->m_amountToEdit->setText(QString());
0063     d->ui->m_amountButton->setChecked(true);
0064     d->ui->m_amountRangeButton->setChecked(false);
0065 
0066     d->ui->m_emptyPayeesButton->setChecked(false);
0067     d->selectAllItems(d->ui->m_payeesView, true);
0068 
0069     d->ui->m_emptyTagsButton->setChecked(false);
0070     d->selectAllItems(d->ui->m_tagsView, true);
0071 
0072     d->ui->m_typeBox->setCurrentIndex((int)eMyMoney::TransactionFilter::Type::All);
0073     d->ui->m_stateBox->setCurrentIndex((int)eMyMoney::TransactionFilter::State::All);
0074     d->ui->m_validityBox->setCurrentIndex((int)eMyMoney::TransactionFilter::Validity::Any);
0075 
0076     d->ui->m_nrEdit->setEnabled(true);
0077     d->ui->m_nrFromEdit->setEnabled(false);
0078     d->ui->m_nrToEdit->setEnabled(false);
0079     d->ui->m_nrEdit->setText(QString());
0080     d->ui->m_nrFromEdit->setText(QString());
0081     d->ui->m_nrToEdit->setText(QString());
0082     d->ui->m_nrButton->setChecked(true);
0083     d->ui->m_nrRangeButton->setChecked(false);
0084 
0085     // the following call implies a call to slotUpdateSelections,
0086     // that's why we call it last
0087     if (d->m_dateRange)
0088         d->m_dateRange->slotReset();
0089     slotUpdateSelections();
0090 
0091     d->ui->m_accountsView->slotSelectAllAccounts();
0092     d->ui->m_categoriesView->slotSelectAllAccounts();
0093 }
0094 
0095 void KTransactionFilter::slotUpdateSelections()
0096 {
0097     Q_D(KTransactionFilter);
0098     QString txt;
0099     const QString separator(", ");
0100     // Text tab
0101     if (!d->ui->m_textEdit->text().isEmpty()) {
0102         if (!txt.isEmpty())
0103             txt += separator;
0104         txt += i18n("Text");
0105         d->ui->m_regExp->setEnabled(QRegularExpression(d->ui->m_textEdit->text()).isValid());
0106     } else
0107         d->ui->m_regExp->setEnabled(false);
0108 
0109     d->ui->m_caseSensitive->setEnabled(!d->ui->m_textEdit->text().isEmpty());
0110     d->ui->m_textNegate->setEnabled(!d->ui->m_textEdit->text().isEmpty());
0111 
0112     // Account tab
0113     if (!d->ui->m_accountsView->allItemsSelected()) {
0114         if (!txt.isEmpty())
0115             txt += separator;
0116         txt += i18n("Account");
0117     }
0118 
0119     if (d->m_dateRange && d->m_dateRange->dateRange() != eMyMoney::TransactionFilter::Date::All) {
0120         if (!txt.isEmpty())
0121             txt += separator;
0122         txt += i18n("Date");
0123     }
0124 
0125     // Amount tab
0126     if ((d->ui->m_amountButton->isChecked() && d->ui->m_amountEdit->isValid())
0127             || (d->ui->m_amountRangeButton->isChecked()
0128                 && (d->ui->m_amountFromEdit->isValid() || d->ui->m_amountToEdit->isValid()))) {
0129         if (!txt.isEmpty())
0130             txt += separator;
0131         txt += i18n("Amount");
0132     }
0133 
0134     // Categories tab
0135     if (!d->ui->m_categoriesView->allItemsSelected()) {
0136         if (!txt.isEmpty())
0137             txt += separator;
0138         txt += i18n("Category");
0139     }
0140 
0141     // Tags tab
0142     if (!d->allItemsSelected(d->ui->m_tagsView)
0143             || d->ui->m_emptyTagsButton->isChecked()) {
0144         if (!txt.isEmpty())
0145             txt += separator;
0146         txt += i18n("Tags");
0147     }
0148     d->ui->m_tagsView->setEnabled(!d->ui->m_emptyTagsButton->isChecked());
0149 
0150     // Payees tab
0151     if (!d->allItemsSelected(d->ui->m_payeesView)
0152             || d->ui->m_emptyPayeesButton->isChecked()) {
0153         if (!txt.isEmpty())
0154             txt += separator;
0155         txt += i18n("Payees");
0156     }
0157     d->ui->m_payeesView->setEnabled(!d->ui->m_emptyPayeesButton->isChecked());
0158 
0159     // Details tab
0160     if (d->ui->m_typeBox->currentIndex() != 0 //
0161             || d->ui->m_stateBox->currentIndex() != 0 //
0162             || d->ui->m_validityBox->currentIndex() != 0 //
0163             || (d->ui->m_nrButton->isChecked() && d->ui->m_nrEdit->text().length() != 0) //
0164             || (d->ui->m_nrRangeButton->isChecked() //
0165                 && (d->ui->m_nrFromEdit->text().length() != 0 || d->ui->m_nrToEdit->text().length() != 0))) {
0166         if (!txt.isEmpty())
0167             txt += separator;
0168         txt += i18n("Details");
0169     }
0170 
0171     //Show a warning about transfers if Categories are filtered - bug #1523508
0172     if (!d->ui->m_categoriesView->allItemsSelected()) {
0173         d->ui->m_transferWarning->setText(i18n("Warning: Filtering by Category will exclude all transfers from the results."));
0174     } else {
0175         d->ui->m_transferWarning->setText(QString());
0176     }
0177 
0178     // disable the search button if no selection is made
0179     Q_EMIT selectionNotEmpty(!txt.isEmpty());
0180 
0181     if (txt.isEmpty()) {
0182         txt = i18nc("No selection", "(None)");
0183     }
0184     d->ui->m_selectedCriteria->setText(i18n("Current selections: %1", txt));
0185 }
0186 
0187 void KTransactionFilter::slotAmountSelected()
0188 {
0189     Q_D(KTransactionFilter);
0190     d->ui->m_amountEdit->setEnabled(true);
0191     d->ui->m_amountFromEdit->setEnabled(false);
0192     d->ui->m_amountToEdit->setEnabled(false);
0193     slotUpdateSelections();
0194 }
0195 
0196 void KTransactionFilter::slotAmountRangeSelected()
0197 {
0198     Q_D(KTransactionFilter);
0199     d->ui->m_amountEdit->setEnabled(false);
0200     d->ui->m_amountFromEdit->setEnabled(true);
0201     d->ui->m_amountToEdit->setEnabled(true);
0202     slotUpdateSelections();
0203 }
0204 
0205 void KTransactionFilter::slotSelectAllPayees()
0206 {
0207     Q_D(KTransactionFilter);
0208     d->selectAllItems(d->ui->m_payeesView, true);
0209 }
0210 
0211 void KTransactionFilter::slotDeselectAllPayees()
0212 {
0213     Q_D(KTransactionFilter);
0214     d->selectAllItems(d->ui->m_payeesView, false);
0215 }
0216 
0217 void KTransactionFilter::slotSelectAllTags()
0218 {
0219     Q_D(KTransactionFilter);
0220     d->selectAllItems(d->ui->m_tagsView, true);
0221 }
0222 
0223 void KTransactionFilter::slotDeselectAllTags()
0224 {
0225     Q_D(KTransactionFilter);
0226     d->selectAllItems(d->ui->m_tagsView, false);
0227 }
0228 
0229 void KTransactionFilter::slotNrSelected()
0230 {
0231     Q_D(KTransactionFilter);
0232     d->ui->m_nrEdit->setEnabled(true);
0233     d->ui->m_nrFromEdit->setEnabled(false);
0234     d->ui->m_nrToEdit->setEnabled(false);
0235     slotUpdateSelections();
0236 }
0237 
0238 void KTransactionFilter::slotNrRangeSelected()
0239 {
0240     Q_D(KTransactionFilter);
0241     d->ui->m_nrEdit->setEnabled(false);
0242     d->ui->m_nrFromEdit->setEnabled(true);
0243     d->ui->m_nrToEdit->setEnabled(true);
0244     slotUpdateSelections();
0245 }
0246 
0247 void KTransactionFilter::slotShowHelp()
0248 {
0249     Q_D(KTransactionFilter);
0250     auto anchor = d->m_helpAnchor[d->ui->m_criteriaTab->currentWidget()];
0251     if (anchor.isEmpty())
0252         anchor = QString("details.search");
0253 
0254     KHelpClient::invokeHelp(anchor);
0255 }
0256 
0257 MyMoneyTransactionFilter KTransactionFilter::setupFilter()
0258 {
0259     Q_D(KTransactionFilter);
0260     d->m_filter.clear();
0261 
0262     // Text tab
0263     if (!d->ui->m_textEdit->text().isEmpty()) {
0264         QRegularExpression exp;
0265         auto pattern(d->ui->m_textEdit->text());
0266         if (!d->ui->m_regExp->isChecked()) {
0267             pattern = MyMoneyUtils::convertWildcardToRegularExpression(pattern);
0268         }
0269         exp.setPattern(pattern);
0270         exp.setPatternOptions(d->ui->m_caseSensitive->isChecked() ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption);
0271         d->m_filter.setTextFilter(exp, d->ui->m_regExp->isChecked(), d->ui->m_textNegate->currentIndex() != 0);
0272     }
0273 
0274     // Account tab
0275     if (!d->ui->m_accountsView->allItemsSelected()) {
0276         // retrieve a list of selected accounts
0277         QStringList list;
0278         d->ui->m_accountsView->selectedItems(list);
0279 
0280         // if we're not in expert mode, we need to make sure
0281         // that all stock accounts for the selected investment
0282         // account are also selected
0283         if (!KMyMoneySettings::expertMode()) {
0284             QStringList missing;
0285             for (const auto& selection : qAsConst(list)) {
0286                 const auto acc = MyMoneyFile::instance()->account(selection);
0287                 if (acc.accountType() == eMyMoney::Account::Type::Investment) {
0288                     for (const auto& sAccount : acc.accountList()) {
0289                         if (!list.contains(sAccount)) {
0290                             missing.append(sAccount);
0291                         }
0292                     }
0293                 }
0294             }
0295             list += missing;
0296         }
0297 
0298         d->m_filter.addAccount(list);
0299     }
0300 
0301     // Date tab
0302     if (d->m_dateRange && (int)d->m_dateRange->dateRange() != 0) {
0303         d->m_filter.setDateFilter(d->m_dateRange->fromDate(), d->m_dateRange->toDate());
0304     }
0305 
0306     // Amount tab
0307     if ((d->ui->m_amountButton->isChecked() && d->ui->m_amountEdit->isValid())) {
0308         d->m_filter.setAmountFilter(d->ui->m_amountEdit->value(), d->ui->m_amountEdit->value());
0309 
0310     } else if ((d->ui->m_amountRangeButton->isChecked() //
0311                 && (d->ui->m_amountFromEdit->isValid() || d->ui->m_amountToEdit->isValid()))) {
0312 
0313         MyMoneyMoney from(MyMoneyMoney::minValue), to(MyMoneyMoney::maxValue);
0314         if (d->ui->m_amountFromEdit->isValid())
0315             from = d->ui->m_amountFromEdit->value();
0316         if (d->ui->m_amountToEdit->isValid())
0317             to = d->ui->m_amountToEdit->value();
0318 
0319         d->m_filter.setAmountFilter(from, to);
0320     }
0321 
0322     // Categories tab
0323     if (!d->ui->m_categoriesView->allItemsSelected()) {
0324         d->m_filter.addCategory(d->ui->m_categoriesView->selectedItems());
0325     }
0326 
0327     // Tags tab
0328     if (d->ui->m_emptyTagsButton->isChecked()) {
0329         d->m_filter.addTag(QString());
0330 
0331     } else if (!d->allItemsSelected(d->ui->m_tagsView)) {
0332         d->scanCheckListItems(d->ui->m_tagsView, KTransactionFilterPrivate::addTagToFilter);
0333     }
0334 
0335     // Payees tab
0336     if (d->ui->m_emptyPayeesButton->isChecked()) {
0337         d->m_filter.addPayee(QString());
0338 
0339     } else if (!d->allItemsSelected(d->ui->m_payeesView)) {
0340         d->scanCheckListItems(d->ui->m_payeesView, KTransactionFilterPrivate::addPayeeToFilter);
0341     }
0342 
0343     // Details tab
0344     if (d->ui->m_typeBox->currentIndex() != 0)
0345         d->m_filter.addType(d->ui->m_typeBox->currentIndex());
0346 
0347     if (d->ui->m_stateBox->currentIndex() != 0)
0348         d->m_filter.addState(d->ui->m_stateBox->currentIndex());
0349 
0350     if (d->ui->m_validityBox->currentIndex() != 0)
0351         d->m_filter.addValidity(d->ui->m_validityBox->currentIndex());
0352 
0353     if (d->ui->m_nrButton->isChecked() && !d->ui->m_nrEdit->text().isEmpty())
0354         d->m_filter.setNumberFilter(d->ui->m_nrEdit->text(), d->ui->m_nrEdit->text());
0355 
0356     if (d->ui->m_nrRangeButton->isChecked() //
0357             && (!d->ui->m_nrFromEdit->text().isEmpty() || !d->ui->m_nrToEdit->text().isEmpty())) {
0358         d->m_filter.setNumberFilter(d->ui->m_nrFromEdit->text(), d->ui->m_nrToEdit->text());
0359     }
0360     return d->m_filter;
0361 }
0362 
0363 void KTransactionFilter::resetFilter(MyMoneyReport& rep)
0364 {
0365     Q_D(KTransactionFilter);
0366     //
0367     // Text Filter
0368     //
0369 
0370     QRegularExpression textfilter;
0371     bool isRegExp(false);
0372     if (rep.textFilter(textfilter, isRegExp)) {
0373         auto pattern(textfilter.pattern());
0374         if (!isRegExp) {
0375             pattern = MyMoneyUtils::convertRegularExpressionToWildcard(pattern);
0376         }
0377         d->ui->m_textEdit->setText(pattern);
0378         d->ui->m_caseSensitive->setChecked(!(textfilter.patternOptions() & QRegularExpression::CaseInsensitiveOption));
0379         d->ui->m_regExp->setChecked(isRegExp);
0380         d->ui->m_textNegate->setCurrentIndex(rep.isInvertingText());
0381     }
0382 
0383     //
0384     // Type & State Filters
0385     //
0386 
0387     int type;
0388     if (rep.firstType(type))
0389         d->ui->m_typeBox->setCurrentIndex(type);
0390 
0391     int state;
0392     if (rep.firstState(state))
0393         d->ui->m_stateBox->setCurrentIndex(state);
0394 
0395     int validity;
0396     if (rep.firstValidity(validity))
0397         d->ui->m_validityBox->setCurrentIndex(validity);
0398 
0399     //
0400     // Number Filter
0401     //
0402 
0403     QString nrFrom, nrTo;
0404     if (rep.numberFilter(nrFrom, nrTo)) {
0405         if (nrFrom == nrTo) {
0406             d->ui->m_nrEdit->setEnabled(true);
0407             d->ui->m_nrFromEdit->setEnabled(false);
0408             d->ui->m_nrToEdit->setEnabled(false);
0409             d->ui->m_nrEdit->setText(nrFrom);
0410             d->ui->m_nrFromEdit->setText(QString());
0411             d->ui->m_nrToEdit->setText(QString());
0412             d->ui->m_nrButton->setChecked(true);
0413             d->ui->m_nrRangeButton->setChecked(false);
0414         } else {
0415             d->ui->m_nrEdit->setEnabled(false);
0416             d->ui->m_nrFromEdit->setEnabled(true);
0417             d->ui->m_nrToEdit->setEnabled(false);
0418             d->ui->m_nrEdit->setText(QString());
0419             d->ui->m_nrFromEdit->setText(nrFrom);
0420             d->ui->m_nrToEdit->setText(nrTo);
0421             d->ui->m_nrButton->setChecked(false);
0422             d->ui->m_nrRangeButton->setChecked(true);
0423         }
0424     } else {
0425         d->ui->m_nrEdit->setEnabled(true);
0426         d->ui->m_nrFromEdit->setEnabled(false);
0427         d->ui->m_nrToEdit->setEnabled(false);
0428         d->ui->m_nrEdit->setText(QString());
0429         d->ui->m_nrFromEdit->setText(QString());
0430         d->ui->m_nrToEdit->setText(QString());
0431         d->ui->m_nrButton->setChecked(true);
0432         d->ui->m_nrRangeButton->setChecked(false);
0433     }
0434 
0435     //
0436     // Amount Filter
0437     //
0438 
0439     MyMoneyMoney from, to;
0440     if (rep.amountFilter(from, to)) { // bool getAmountFilter(MyMoneyMoney&,MyMoneyMoney&);
0441         if (from == to) {
0442             d->ui->m_amountEdit->setEnabled(true);
0443             d->ui->m_amountFromEdit->setEnabled(false);
0444             d->ui->m_amountToEdit->setEnabled(false);
0445             d->ui->m_amountEdit->setText(QString::number(from.toDouble()));
0446             d->ui->m_amountFromEdit->setText(QString());
0447             d->ui->m_amountToEdit->setText(QString());
0448             d->ui->m_amountButton->setChecked(true);
0449             d->ui->m_amountRangeButton->setChecked(false);
0450         } else {
0451             d->ui->m_amountEdit->setEnabled(false);
0452             d->ui->m_amountFromEdit->setEnabled(true);
0453             d->ui->m_amountToEdit->setEnabled(true);
0454             d->ui->m_amountEdit->setText(QString());
0455             d->ui->m_amountFromEdit->setText(QString::number(from.toDouble()));
0456             d->ui->m_amountToEdit->setText(QString::number(to.toDouble()));
0457             d->ui->m_amountButton->setChecked(false);
0458             d->ui->m_amountRangeButton->setChecked(true);
0459         }
0460     } else {
0461         d->ui->m_amountEdit->setEnabled(true);
0462         d->ui->m_amountFromEdit->setEnabled(false);
0463         d->ui->m_amountToEdit->setEnabled(false);
0464         d->ui->m_amountEdit->setText(QString());
0465         d->ui->m_amountFromEdit->setText(QString());
0466         d->ui->m_amountToEdit->setText(QString());
0467         d->ui->m_amountButton->setChecked(true);
0468         d->ui->m_amountRangeButton->setChecked(false);
0469     }
0470 
0471     //
0472     // Payees Filter
0473     //
0474 
0475     QStringList payees;
0476     if (rep.payees(payees)) {
0477         if (payees.empty()) {
0478             d->ui->m_emptyPayeesButton->setChecked(true);
0479         } else {
0480             d->selectAllItems(d->ui->m_payeesView, false);
0481             d->selectItems(d->ui->m_payeesView, payees, true);
0482         }
0483     } else {
0484         d->selectAllItems(d->ui->m_payeesView, true);
0485     }
0486 
0487     //
0488     // Tags Filter
0489     //
0490 
0491     QStringList tags;
0492     if (rep.tags(tags)) {
0493         if (tags.empty()) {
0494             d->ui->m_emptyTagsButton->setChecked(true);
0495         } else {
0496             d->selectAllItems(d->ui->m_tagsView, false);
0497             d->selectItems(d->ui->m_tagsView, tags, true);
0498         }
0499     } else {
0500         d->selectAllItems(d->ui->m_tagsView, true);
0501     }
0502 
0503     //
0504     // Accounts Filter
0505     //
0506 
0507     QStringList accounts;
0508     if (rep.accounts(accounts)) {
0509         // in case the presentation of closed accounts is turned off ...
0510         if (d->accountSet.isHidingClosedAccounts()) {
0511             // ... we need to turn them on again in case our own
0512             // configuration references a closed account
0513             const MyMoneyFile* file = MyMoneyFile::instance();
0514             for (const auto& accId : qAsConst(accounts)) {
0515                 try {
0516                     if (file->account(accId).isClosed()) {
0517                         d->accountSet.setHideClosedAccounts(false);
0518                         d->accountSet.load(d->ui->m_accountsView);
0519                         break;
0520                     }
0521                 } catch (const MyMoneyException&) {
0522                 }
0523             }
0524         }
0525         d->ui->m_accountsView->selectAllItems(false);
0526         d->ui->m_accountsView->selectItems(accounts, true);
0527     } else
0528         d->ui->m_accountsView->selectAllItems(true);
0529 
0530     //
0531     // Categories Filter
0532     //
0533 
0534     if (rep.categories(accounts)) {
0535         d->ui->m_categoriesView->selectAllItems(false);
0536         d->ui->m_categoriesView->selectItems(accounts, true);
0537     } else
0538         d->ui->m_categoriesView->selectAllItems(true);
0539 
0540     //
0541     // Date Filter
0542     //
0543 
0544     // the following call implies a call to slotUpdateSelections,
0545     // that's why we call it last
0546 
0547     if (d->m_dateRange) {
0548         rep.updateDateFilter();
0549         QDate dateFrom, dateTo;
0550         if (rep.dateFilter(dateFrom, dateTo)) {
0551             if (rep.isDateUserDefined()) {
0552                 d->m_dateRange->setDateRange(dateFrom, dateTo);
0553             } else {
0554                 d->m_dateRange->setDateRange(rep.dateRange());
0555             }
0556         } else {
0557             d->m_dateRange->setDateRange(eMyMoney::TransactionFilter::Date::All);
0558         }
0559     }
0560 
0561 }
0562 
0563 KMyMoneyAccountSelector* KTransactionFilter::categoriesView()
0564 {
0565     Q_D(KTransactionFilter);
0566     return d->ui->m_categoriesView;
0567 }
0568 
0569 DateRangeDlg* KTransactionFilter::dateRange()
0570 {
0571     Q_D(KTransactionFilter);
0572     return d->m_dateRange;
0573 }