File indexing completed on 2024-05-19 05:06:57

0001 /*
0002     SPDX-FileCopyrightText: 2020 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "ledgerfilter.h"
0007 
0008 // ----------------------------------------------------------------------------
0009 // QT Includes
0010 
0011 #include <QComboBox>
0012 #include <QLineEdit>
0013 #include <QTimer>
0014 #include <QDebug>
0015 
0016 // ----------------------------------------------------------------------------
0017 // KDE Includes
0018 
0019 #include <KLocalizedString>
0020 
0021 // ----------------------------------------------------------------------------
0022 // Project Includes
0023 
0024 #include "icons.h"
0025 #include "journalmodel.h"
0026 #include "ledgersortproxymodel_p.h"
0027 #include "mymoneyaccount.h"
0028 #include "mymoneyenums.h"
0029 #include "mymoneyfile.h"
0030 #include "mymoneymodelbase.h"
0031 #include "mymoneymoney.h"
0032 #include "mymoneytag.h"
0033 #include "reconciliationmodel.h"
0034 #include "schedulesjournalmodel.h"
0035 
0036 using namespace Icons;
0037 
0038 class LedgerFilterPrivate : public LedgerSortProxyModelPrivate
0039 {
0040 public:
0041     explicit LedgerFilterPrivate(LedgerFilter* qq)
0042         : LedgerSortProxyModelPrivate(qq)
0043         , lineEdit(nullptr)
0044         , comboBox(nullptr)
0045         , state(LedgerFilter::State::Any)
0046     {
0047         delayTimer.setSingleShot(true);
0048     }
0049 
0050     QLineEdit* lineEdit;
0051     QComboBox* comboBox;
0052     LedgerFilter::State state;
0053     QString filterString;
0054     QTimer delayTimer;
0055     QDate endDate;
0056 };
0057 
0058 LedgerFilter::LedgerFilter(QObject* parent)
0059     : LedgerSortProxyModel(new LedgerFilterPrivate(this), parent)
0060 {
0061     Q_D(LedgerFilter);
0062     setObjectName("LedgerFilter");
0063     connect(&d->delayTimer, &QTimer::timeout, this, [&]() {
0064         invalidateFilter();
0065     });
0066 }
0067 
0068 void LedgerFilter::setComboBox(QComboBox* filterBox)
0069 {
0070     Q_D(LedgerFilter);
0071     filterBox->clear();
0072     filterBox->insertItem(static_cast<int>(State::Any), Icons::get(Icon::TransactionStateAny), i18n("Any status"));
0073     filterBox->insertItem(static_cast<int>(State::Imported), Icons::get(Icon::TransactionStateImported), i18n("Imported"));
0074     filterBox->insertItem(static_cast<int>(State::Matched), Icons::get(Icon::TransactionStateMatched), i18n("Matched"));
0075     filterBox->insertItem(static_cast<int>(State::Erroneous), Icons::get(Icon::TransactionStateErroneous), i18n("Erroneous"));
0076     filterBox->insertItem(static_cast<int>(State::Scheduled), Icons::get(Icon::TransactionStateScheduled), i18n("Scheduled"));
0077     filterBox->insertItem(static_cast<int>(State::NotMarked), Icons::get(Icon::TransactionStateNotMarked), i18n("Not marked"));
0078     filterBox->insertItem(static_cast<int>(State::NotReconciled), Icons::get(Icon::TransactionStateNotReconciled), i18n("Not reconciled"));
0079     filterBox->insertItem(static_cast<int>(State::Cleared), Icons::get(Icon::TransactionStateCleared), i18nc("Reconciliation state 'Cleared'", "Cleared"));
0080     filterBox->setCurrentIndex(static_cast<int>(d->state));
0081 
0082     // connect(d->ui->m_filterBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &RegisterSearchLine::slotStatusChanged);
0083     connect(filterBox, QOverload<int>::of(&QComboBox::activated), this, [&](int idx) {
0084         setStateFilter(static_cast<LedgerFilter::State>(idx));
0085     });
0086     connect(filterBox, &QComboBox::destroyed, this, [&]() {
0087         Q_D(LedgerFilter);
0088         d->comboBox = nullptr;
0089     });
0090 
0091     d->comboBox = filterBox;
0092 }
0093 
0094 void LedgerFilter::setLineEdit(QLineEdit* lineEdit)
0095 {
0096     Q_D(LedgerFilter);
0097     Q_ASSERT(lineEdit != nullptr);
0098     lineEdit->setClearButtonEnabled(true);
0099     connect(lineEdit, &QLineEdit::textChanged, this, [&](const QString& text) {
0100         Q_D(LedgerFilter);
0101         d->filterString = text;
0102         d->delayTimer.start(200);
0103     });
0104     connect(lineEdit, &QLineEdit::destroyed, this, [&]() {
0105         Q_D(LedgerFilter);
0106         d->lineEdit = nullptr;
0107     });
0108     d->lineEdit = lineEdit;
0109 }
0110 
0111 bool LedgerFilter::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
0112 {
0113     Q_D(const LedgerFilter);
0114 
0115     const auto idx = sourceModel()->index(source_row, 0, source_parent);
0116     const bool isJournalItem = d->isJournalModel(idx);
0117     const bool isScheduleItem = d->isSchedulesJournalModel(idx);
0118 
0119     // a transaction is in date range if no date range is set or the post date is older than the end of the range
0120     const auto inDateRange = !d->endDate.isValid() || (idx.data(eMyMoney::Model::TransactionPostDateRole).toDate() <= d->endDate);
0121 
0122     if (d->state != State::Any) {
0123         if (isJournalItem) {
0124             const auto splitState = idx.data(eMyMoney::Model::SplitReconcileFlagRole).value<eMyMoney::Split::State>();
0125             switch (d->state) {
0126             case State::NotMarked:
0127                 if ((splitState != eMyMoney::Split::State::NotReconciled) && inDateRange) {
0128                     return false;
0129                 }
0130                 break;
0131             case State::Cleared:
0132                 if ((splitState != eMyMoney::Split::State::Cleared) && inDateRange) {
0133                     return false;
0134                 }
0135                 break;
0136             case State::NotReconciled:
0137                 if (((splitState == eMyMoney::Split::State::Reconciled) || (splitState == eMyMoney::Split::State::Frozen)) && inDateRange) {
0138                     return false;
0139                 }
0140                 break;
0141             case State::Erroneous:
0142                 if (!idx.data(eMyMoney::Model::TransactionErroneousRole).toBool() && inDateRange) {
0143                     return false;
0144                 }
0145                 break;
0146             case State::Imported:
0147                 if (!idx.data(eMyMoney::Model::TransactionIsImportedRole).toBool() && inDateRange) {
0148                     return false;
0149                 }
0150                 break;
0151             case State::Matched:
0152                 if (!idx.data(eMyMoney::Model::JournalSplitIsMatchedRole).toBool() && inDateRange) {
0153                     return false;
0154                 }
0155                 break;
0156 
0157             case State::Scheduled:
0158                 return false;
0159 
0160             default:
0161                 break;
0162             }
0163         }
0164 
0165         if (isScheduleItem) {
0166             if (d->state != State::Scheduled) {
0167                 return false;
0168             }
0169         }
0170     }
0171 
0172     if (isJournalItem || isScheduleItem) {
0173         if (!d->filterString.isEmpty()) {
0174             const auto file = MyMoneyFile::instance();
0175             auto rc = idx.data(eMyMoney::Model::SplitMemoRole).toString().contains(d->filterString, Qt::CaseInsensitive);
0176             if (!rc)
0177                 rc = idx.data(eMyMoney::Model::SplitNumberRole).toString().contains(d->filterString, Qt::CaseInsensitive);
0178             if (!rc)
0179                 rc = idx.data(eMyMoney::Model::SplitPayeeRole).toString().contains(d->filterString, Qt::CaseInsensitive);
0180             if (!rc) {
0181                 const auto tagIdList = idx.data(eMyMoney::Model::SplitTagIdRole).toStringList();
0182                 for (const auto& tagId : tagIdList) {
0183                     const auto tagName = file->tag(tagId).name();
0184                     rc = tagName.contains(d->filterString, Qt::CaseInsensitive);
0185                     if (rc)
0186                         break;
0187                 }
0188             }
0189             if (!rc) {
0190                 const auto accId = idx.data(eMyMoney::Model::SplitAccountIdRole).toString();
0191                 if (!accId.isEmpty()) {
0192                     const auto acc = file->account(accId);
0193                     if (d->filterString.contains(MyMoneyFile::AccountSeparator)) {
0194                         QStringList names;
0195                         MyMoneyAccount current = acc;
0196                         QString accountId;
0197                         do {
0198                             names.prepend(current.name());
0199                             accountId = current.parentAccountId();
0200                             current = file->account(accountId);
0201                         } while (current.accountType() != eMyMoney::Account::Type::Unknown && !MyMoneyFile::instance()->isStandardAccount(accountId));
0202                         if (names.size() > 1 && names.join(MyMoneyFile::AccountSeparator).contains(d->filterString, Qt::CaseInsensitive))
0203                             rc = true;
0204                     }
0205 
0206                     if (!rc) {
0207                         rc = acc.name().contains(d->filterString, Qt::CaseInsensitive);
0208                     }
0209                 }
0210             }
0211 
0212             if (!rc) {
0213                 QString s(d->filterString);
0214                 s.replace(MyMoneyMoney::thousandSeparator(), QChar());
0215                 if (!s.isEmpty()) {
0216                     rc = idx.data(eMyMoney::Model::SplitFormattedValueRole).toString().contains(s, Qt::CaseInsensitive);
0217                     if (!rc)
0218                         rc = idx.data(eMyMoney::Model::SplitFormattedSharesRole).toString().contains(s, Qt::CaseInsensitive);
0219                 }
0220             }
0221             if (!rc)
0222                 return false;
0223         }
0224     }
0225 
0226     // Don't call base class here on purpose.
0227     // We've done all the filtering that need's to be done.
0228     return true;
0229 }
0230 
0231 QVariant LedgerFilter::data(const QModelIndex& index, int role) const
0232 {
0233     Q_D(const LedgerFilter);
0234     switch (role) {
0235     case eMyMoney::Model::ActiveFilterRole:
0236         return !d->filterString.isEmpty() || (d->state != State::Any);
0237     case eMyMoney::Model::ActiveFilterTextRole:
0238         return !d->filterString.isEmpty();
0239     case eMyMoney::Model::ActiveFilterStateRole:
0240         return QVariant::fromValue<LedgerFilter::State>(d->state);
0241     }
0242     return LedgerSortProxyModel::data(index, role);
0243 }
0244 
0245 void LedgerFilter::setStateFilter(LedgerFilter::State state)
0246 {
0247     Q_D(LedgerFilter);
0248     d->state = state;
0249     invalidateFilter();
0250 }
0251 
0252 void LedgerFilter::setFilterFixedString(const QString& pattern)
0253 {
0254     Q_D(LedgerFilter);
0255     d->filterString = pattern;
0256     invalidateFilter();
0257 }
0258 
0259 void LedgerFilter::clearFilter()
0260 {
0261     Q_D(LedgerFilter);
0262     d->filterString.clear();
0263     d->state = State::Any;
0264     d->endDate = QDate();
0265     if (d->lineEdit)
0266         d->lineEdit->clear();
0267     if (d->comboBox)
0268         d->comboBox->setCurrentIndex(0);
0269     invalidateFilter();
0270 }
0271 
0272 void LedgerFilter::setEndDate(const QDate& endDate)
0273 {
0274     Q_D(LedgerFilter);
0275     if (d->endDate != endDate) {
0276         d->endDate = endDate;
0277         invalidateFilter();
0278     }
0279 }