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 }