File indexing completed on 2024-05-19 05:08:20
0001 /* 0002 SPDX-FileCopyrightText: 2000-2002 Michael Edwardes <mte@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2000-2002 Javier Campos Morales <javi_c@users.sourceforge.net> 0004 SPDX-FileCopyrightText: 2000-2002 Felix Rodriguez <frodriguez@users.sourceforge.net> 0005 SPDX-FileCopyrightText: 2000-2002 John C <thetacoturtle@users.sourceforge.net> 0006 SPDX-FileCopyrightText: 2000-2002 Thomas Baumgart <ipwizard@users.sourceforge.net> 0007 SPDX-FileCopyrightText: 2000-2002 Kevin Tambascio <ktambascio@users.sourceforge.net> 0008 SPDX-FileCopyrightText: 2000-2002 Andreas Nicolai <Andreas.Nicolai@gmx.net> 0009 SPDX-FileCopyrightText: 2017 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> 0010 SPDX-License-Identifier: GPL-2.0-or-later 0011 */ 0012 0013 #ifndef KPAYEESVIEW_P_H 0014 #define KPAYEESVIEW_P_H 0015 0016 // ---------------------------------------------------------------------------- 0017 // QT Includes 0018 0019 #include <QDesktopServices> 0020 #include <QRegularExpression> 0021 0022 // ---------------------------------------------------------------------------- 0023 // KDE Includes 0024 0025 #include <KLocalizedString> 0026 #include <KMessageBox> 0027 #include <KHelpClient> 0028 #include <KSharedConfig> 0029 0030 // ---------------------------------------------------------------------------- 0031 // Project Includes 0032 0033 #include "config-kmymoney.h" 0034 0035 #include "accountsmodel.h" 0036 #include "icons.h" 0037 #include "itemrenameproxymodel.h" 0038 #include "journalmodel.h" 0039 #include "kmymoneymvccombo.h" 0040 #include "kmymoneysettings.h" 0041 #include "kmymoneyutils.h" 0042 #include "kmymoneyviewbase_p.h" 0043 #include "kpayeereassigndlg.h" 0044 #include "ledgerpayeefilter.h" 0045 #include "ledgerviewsettings.h" 0046 #include "menuenums.h" 0047 #include "mymoneyaccount.h" 0048 #include "mymoneyaccountloan.h" 0049 #include "mymoneycontact.h" 0050 #include "mymoneyenums.h" 0051 #include "mymoneyexception.h" 0052 #include "mymoneyfile.h" 0053 #include "mymoneymoney.h" 0054 #include "mymoneypayee.h" 0055 #include "mymoneyprice.h" 0056 #include "mymoneyschedule.h" 0057 #include "mymoneysecurity.h" 0058 #include "mymoneysplit.h" 0059 #include "mymoneytransaction.h" 0060 #include "mymoneytransactionfilter.h" 0061 #include "payeesmodel.h" 0062 #include "specialdatesmodel.h" 0063 #include "specialledgeritemfilter.h" 0064 0065 #include "ui_kpayeesview.h" 0066 0067 using namespace Icons; 0068 0069 enum filterTypeE { eAllPayees = 0, eReferencedPayees = 1, eUnusedPayees = 2 }; 0070 0071 class KPayeesViewPrivate : public KMyMoneyViewBasePrivate 0072 { 0073 Q_DECLARE_PUBLIC(KPayeesView) 0074 0075 public: 0076 enum struct eTransactionDisplay { 0077 ShowTransactions, 0078 ClearTransactionDisplay, 0079 }; 0080 0081 explicit KPayeesViewPrivate(KPayeesView* qq) 0082 : KMyMoneyViewBasePrivate(qq) 0083 , ui(new Ui::KPayeesView) 0084 , m_transactionFilter(nullptr) 0085 , m_contact(nullptr) 0086 , m_syncedPayees(0) 0087 , m_filterProxyModel(nullptr) 0088 , m_renameProxyModel(nullptr) 0089 { 0090 } 0091 0092 ~KPayeesViewPrivate() 0093 { 0094 // remember the splitter settings for startup 0095 auto grp = KSharedConfig::openConfig()->group("Last Use Settings"); 0096 grp.writeEntry("KPayeesViewSplitterSize", ui->m_splitter->saveState()); 0097 grp.sync(); 0098 delete ui; 0099 } 0100 0101 void init() 0102 { 0103 Q_Q(KPayeesView); 0104 ui->setupUi(q); 0105 0106 ui->m_register->setSingleLineDetailRole(eMyMoney::Model::TransactionCounterAccountRole); 0107 ui->m_register->setColumnSelectorGroupName(QLatin1String("PayeeLedger")); 0108 ui->m_register->setShowPayeeInDetailColumn(false); 0109 0110 // setup the model stack 0111 auto file = MyMoneyFile::instance(); 0112 m_transactionFilter = new LedgerPayeeFilter(ui->m_register, QVector<QAbstractItemModel*> { file->specialDatesModel() }); 0113 m_transactionFilter->setHideReconciledTransactions(LedgerViewSettings::instance()->hideReconciledTransactions()); 0114 m_transactionFilter->setHideTransactionsBefore(LedgerViewSettings::instance()->hideTransactionsBefore()); 0115 0116 auto specialItemFilter = new SpecialLedgerItemFilter(q); 0117 specialItemFilter->setSourceModel(m_transactionFilter); 0118 ui->m_register->setModel(specialItemFilter); 0119 0120 // keep track of changing balances 0121 q->connect(file->journalModel(), &JournalModel::balanceChanged, m_transactionFilter, &LedgerPayeeFilter::recalculateBalancesOnIdle); 0122 0123 m_filterProxyModel = new AccountNamesFilterProxyModel(q); 0124 m_filterProxyModel->setHideEquityAccounts(!KMyMoneySettings::expertMode()); 0125 m_filterProxyModel->setHideZeroBalancedEquityAccounts(KMyMoneySettings::hideZeroBalanceEquities()); 0126 m_filterProxyModel->setHideZeroBalancedAccounts(KMyMoneySettings::hideZeroBalanceAccounts()); 0127 m_filterProxyModel->addAccountGroup(QVector<eMyMoney::Account::Type> {eMyMoney::Account::Type::Asset, eMyMoney::Account::Type::Liability, eMyMoney::Account::Type::Income, eMyMoney::Account::Type::Expense, eMyMoney::Account::Type::Equity}); 0128 0129 m_filterProxyModel->setSourceModel(MyMoneyFile::instance()->accountsModel()); 0130 m_filterProxyModel->sort(AccountsModel::Column::AccountName); 0131 0132 0133 ui->comboDefaultCategory->setModel(m_filterProxyModel); 0134 0135 ui->matchTypeCombo->addItem(i18nc("@item No matching", "No matching"), static_cast<int>(eMyMoney::Payee::MatchType::Disabled)); 0136 ui->matchTypeCombo->addItem(i18nc("@item Match Payees name partially", "Match Payees name (partial)"), static_cast<int>(eMyMoney::Payee::MatchType::Name)); 0137 ui->matchTypeCombo->addItem(i18nc("@item Match Payees name exactly", "Match Payees name (exact)"), static_cast<int>(eMyMoney::Payee::MatchType::NameExact)); 0138 ui->matchTypeCombo->addItem(i18nc("@item Search match in list", "Match on a name listed below"), static_cast<int>(eMyMoney::Payee::MatchType::Key)); 0139 0140 //load the filter type 0141 ui->m_filterBox->addItem(i18nc("@item Show all payees", "All"), ItemRenameProxyModel::eAllItem); 0142 ui->m_filterBox->addItem(i18nc("@item Show only used payees", "Used"), ItemRenameProxyModel::eReferencedItems); 0143 ui->m_filterBox->addItem(i18nc("@item Show only unused payees", "Unused"), ItemRenameProxyModel::eUnReferencedItems); 0144 ui->m_filterBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); 0145 0146 ui->m_updateButton->setIcon(Icons::get(Icon::DialogOK)); 0147 ui->m_syncAddressbook->setIcon(Icons::get(Icon::Refresh)); 0148 ui->m_sendMail->setIcon(Icons::get(Icon::MailMessage)); 0149 0150 ui->m_updateButton->setEnabled(false); 0151 ui->m_syncAddressbook->setEnabled(false); 0152 #ifndef ENABLE_ADDRESSBOOK 0153 ui->m_syncAddressbook->hide(); 0154 #endif 0155 ui->matchTypeCombo->setCurrentIndex(0); 0156 0157 ui->checkMatchIgnoreCase->setEnabled(false); 0158 0159 ui->checkEnableDefaultCategory->setChecked(false); 0160 ui->labelDefaultCategory->setEnabled(false); 0161 ui->comboDefaultCategory->setEnabled(false); 0162 0163 m_renameProxyModel = new ItemRenameProxyModel(q); 0164 ui->m_payees->setModel(m_renameProxyModel); 0165 ui->m_payees->setContextMenuPolicy(Qt::CustomContextMenu); 0166 0167 m_renameProxyModel->setReferenceFilter(ItemRenameProxyModel::eAllItem); 0168 m_renameProxyModel->setFilterKeyColumn(PayeesModel::Column::Name); 0169 m_renameProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); 0170 m_renameProxyModel->setRenameColumn(PayeesModel::Column::Name); 0171 m_renameProxyModel->setSortRole(eMyMoney::Model::PayeeNameRole); 0172 m_renameProxyModel->setSortLocaleAware(true); 0173 m_renameProxyModel->sort(0); 0174 0175 m_renameProxyModel->setSourceModel(MyMoneyFile::instance()->payeesModel()); 0176 0177 // setup the searchline widget 0178 q->connect(ui->m_searchWidget, &QLineEdit::textChanged, m_renameProxyModel, &QSortFilterProxyModel::setFilterFixedString); 0179 ui->m_searchWidget->setClearButtonEnabled(true); 0180 ui->m_searchWidget->setPlaceholderText(i18nc("Placeholder text", "Search")); 0181 0182 #if 0 0183 ui->m_balanceLabel->hide(); 0184 #endif 0185 0186 m_contact = new MyMoneyContact(q); 0187 q->connect(m_contact, &MyMoneyContact::contactFetched, q, &KPayeesView::slotContactFetched); 0188 0189 q->connect(ui->m_payees->selectionModel(), &QItemSelectionModel::selectionChanged, q, &KPayeesView::slotPayeeSelectionChanged); 0190 ui->m_payees->setSelectionMode(QListView::ExtendedSelection); 0191 q->connect(m_renameProxyModel, &ItemRenameProxyModel::renameItem, q, &KPayeesView::slotRenameSinglePayee); 0192 q->connect(m_renameProxyModel, &ItemRenameProxyModel::dataChanged, q, &KPayeesView::slotModelDataChanged); 0193 0194 ui->m_newButton->setDefaultAction(pActions[eMenu::Action::NewPayee]); 0195 ui->m_renameButton->setDefaultAction(pActions[eMenu::Action::RenamePayee]); 0196 ui->m_deleteButton->setDefaultAction(pActions[eMenu::Action::DeletePayee]); 0197 ui->m_mergeButton->setDefaultAction(pActions[eMenu::Action::MergePayee]); 0198 0199 q->connect(ui->addressEdit, &QTextEdit::textChanged, q, &KPayeesView::slotPayeeDataChanged); 0200 q->connect(ui->payeecityEdit, &QLineEdit::textChanged, q, &KPayeesView::slotPayeeDataChanged); 0201 q->connect(ui->payeestateEdit, &QLineEdit::textChanged, q, &KPayeesView::slotPayeeDataChanged); 0202 q->connect(ui->postcodeEdit, &QLineEdit::textChanged, q, &KPayeesView::slotPayeeDataChanged); 0203 q->connect(ui->telephoneEdit, &QLineEdit::textChanged, q, &KPayeesView::slotPayeeDataChanged); 0204 q->connect(ui->emailEdit, &QLineEdit::textChanged, q, &KPayeesView::slotPayeeDataChanged); 0205 q->connect(ui->notesEdit, &QTextEdit::textChanged, q, &KPayeesView::slotPayeeDataChanged); 0206 q->connect(ui->payeeIdentifiers, &KPayeeIdentifierView::dataChanged, q, &KPayeesView::slotPayeeDataChanged); 0207 q->connect(ui->matchTypeCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), q, &KPayeesView::slotPayeeDataChanged); 0208 q->connect(ui->checkMatchIgnoreCase, &QAbstractButton::toggled, q, &KPayeesView::slotPayeeDataChanged); 0209 q->connect(ui->checkEnableDefaultCategory, &QAbstractButton::toggled, q, &KPayeesView::slotPayeeDataChanged); 0210 q->connect(ui->comboDefaultCategory, &KMyMoneyAccountCombo::accountSelected, q, &KPayeesView::slotPayeeDataChanged); 0211 0212 q->connect(ui->buttonSuggestACategory, &QAbstractButton::clicked, q, &KPayeesView::slotChooseDefaultAccount); 0213 0214 q->connect(ui->matchKeyEditList, &KEditListWidget::changed, q, &KPayeesView::slotKeyListChanged); 0215 0216 q->connect(ui->m_updateButton, &QAbstractButton::clicked, q, &KPayeesView::slotUpdatePayee); 0217 q->connect(ui->m_syncAddressbook, &QAbstractButton::clicked, q, &KPayeesView::slotSyncAddressBook); 0218 q->connect(ui->m_helpButton, &QAbstractButton::clicked, q, &KPayeesView::slotHelp); 0219 q->connect(ui->m_sendMail, &QAbstractButton::clicked, q, &KPayeesView::slotSendMail); 0220 0221 0222 // use the size settings of the last run (if any) 0223 KConfigGroup grp = KSharedConfig::openConfig()->group("Last Use Settings"); 0224 ui->m_splitter->restoreState(grp.readEntry("KPayeesViewSplitterSize", QByteArray())); 0225 ui->m_splitter->setChildrenCollapsible(false); 0226 0227 QVector<int> columns; 0228 columns = { 0229 JournalModel::Column::Number, 0230 JournalModel::Column::Payee, 0231 JournalModel::Column::Security, 0232 JournalModel::Column::CostCenter, 0233 JournalModel::Column::Quantity, 0234 JournalModel::Column::Price, 0235 JournalModel::Column::Amount, 0236 JournalModel::Column::Value, 0237 JournalModel::Column::Balance, 0238 }; 0239 ui->m_register->setColumnsHidden(columns); 0240 columns = { 0241 JournalModel::Column::Date, 0242 JournalModel::Column::Account, 0243 JournalModel::Column::Detail, 0244 JournalModel::Column::Reconciliation, 0245 JournalModel::Column::Payment, 0246 JournalModel::Column::Deposit, 0247 }; 0248 ui->m_register->setColumnsShown(columns); 0249 0250 // At start we haven't any payee selected 0251 ui->m_tabWidget->setEnabled(false); // disable tab widget 0252 0253 m_payee = MyMoneyPayee(); // make sure we don't access an undefined payee 0254 clearItemData(); 0255 0256 m_focusWidget = ui->m_searchWidget; 0257 0258 specialItemFilter->setSortingEnabled(true); 0259 } 0260 0261 void ensurePayeeVisible(const QString& id) 0262 { 0263 const auto baseIdx = MyMoneyFile::instance()->payeesModel()->indexById(id); 0264 if (baseIdx.isValid()) { 0265 const auto idx = MyMoneyFile::baseModel()->mapFromBaseSource(m_renameProxyModel, baseIdx); 0266 ui->m_payees->setCurrentIndex(idx); 0267 ui->m_payees->scrollTo(idx); 0268 } 0269 } 0270 0271 void loadDetails() 0272 { 0273 ui->addressEdit->setText(m_payee.address()); 0274 ui->postcodeEdit->setText(m_payee.postcode()); 0275 ui->telephoneEdit->setText(m_payee.telephone()); 0276 ui->emailEdit->setText(m_payee.email()); 0277 ui->notesEdit->setText(m_payee.notes()); 0278 ui->payeestateEdit->setText(m_payee.state()); 0279 ui->payeecityEdit->setText(m_payee.city()); 0280 0281 QStringList keys; 0282 bool ignorecase = false; 0283 auto type = m_payee.matchData(ignorecase, keys); 0284 0285 ui->matchTypeCombo->setCurrentIndex(ui->matchTypeCombo->findData(static_cast<int>(type))); 0286 ui->matchKeyEditList->clear(); 0287 ui->matchKeyEditList->insertStringList(keys); 0288 ui->checkMatchIgnoreCase->setChecked(ignorecase); 0289 0290 ui->comboDefaultCategory->clearSelection(); 0291 ui->checkEnableDefaultCategory->setChecked(!m_payee.defaultAccountId().isEmpty()); 0292 ui->comboDefaultCategory->setSelected(m_payee.defaultAccountId()); 0293 0294 ui->payeeIdentifiers->setSource(m_payee); 0295 0296 ui->m_tabWidget->setEnabled(!m_payee.id().isEmpty()); 0297 } 0298 0299 void clearItemData() 0300 { 0301 ui->addressEdit->setText(QString()); 0302 ui->payeecityEdit->setText(QString()); 0303 ui->payeestateEdit->setText(QString()); 0304 ui->postcodeEdit->setText(QString()); 0305 ui->telephoneEdit->setText(QString()); 0306 ui->emailEdit->setText(QString()); 0307 ui->notesEdit->setText(QString()); 0308 showTransactions(eTransactionDisplay::ClearTransactionDisplay); 0309 } 0310 0311 QStringList selectedPayeeIds() const 0312 { 0313 QStringList payees; 0314 auto baseModel = MyMoneyFile::instance()->payeesModel(); 0315 const QModelIndexList selection = ui->m_payees->selectionModel()->selectedIndexes(); 0316 for (const auto idx : selection) { 0317 auto baseIdx = baseModel->mapToBaseSource(idx); 0318 const auto id = baseIdx.data(eMyMoney::Model::IdRole).toString(); 0319 if (!id.isEmpty()) { 0320 payees.append(id); 0321 } 0322 } 0323 return payees; 0324 } 0325 0326 QList<MyMoneyPayee> selectedPayees() const 0327 { 0328 QList<MyMoneyPayee> payees; 0329 auto baseModel = MyMoneyFile::instance()->payeesModel(); 0330 const QModelIndexList selection = ui->m_payees->selectionModel()->selectedIndexes(); 0331 for (const auto idx : selection) { 0332 auto baseIdx = baseModel->mapToBaseSource(idx); 0333 auto payee = baseModel->itemByIndex(baseIdx); 0334 if (!payee.id().isEmpty()) { 0335 payees.append(payee); 0336 } 0337 } 0338 return payees; 0339 } 0340 0341 void showTransactions(eTransactionDisplay clearDisplay = eTransactionDisplay::ShowTransactions) 0342 { 0343 MyMoneyMoney balance; 0344 const auto file = MyMoneyFile::instance(); 0345 MyMoneySecurity base = file->baseCurrency(); 0346 0347 const auto selection = selectedPayees(); 0348 0349 QStringList payeeIds; 0350 if (selection.isEmpty() || !ui->m_tabWidget->isEnabled()) { 0351 m_transactionFilter->setPayeeIdList(payeeIds); 0352 ui->m_balanceLabel->setText(i18n("Balance: %1", balance.formatMoney(file->baseCurrency().smallestAccountFraction()))); 0353 return; 0354 } 0355 0356 if (clearDisplay == eTransactionDisplay::ShowTransactions) { 0357 for (const auto& payee : selection) { 0358 payeeIds.append(payee.id()); 0359 } 0360 } 0361 m_transactionFilter->setPayeeIdList(payeeIds); 0362 0363 bool balanceAccurate = true; 0364 QModelIndex idx; 0365 MyMoneyMoney deposit, payment; 0366 const auto rows = m_transactionFilter->rowCount(); 0367 for(int row = 0; row < rows; ++row) { 0368 idx = m_transactionFilter->index(row, 0); 0369 const auto accountId = idx.data(eMyMoney::Model::SplitAccountIdRole).toString(); 0370 // in case of an entry of the specialDatesModel the accountId is empty 0371 if (!accountId.isEmpty()) { 0372 const auto acc = file->account(accountId); 0373 0374 const auto shares = idx.data(eMyMoney::Model::SplitSharesRole).value<MyMoneyMoney>(); 0375 auto val = shares.abs(); 0376 if (acc.currencyId() != base.id()) { 0377 const MyMoneyPrice &price = file->price(acc.currencyId(), base.id()); 0378 // in case the price is valid, we use it. Otherwise, we keep 0379 // a flag that tells us that the balance is somewhat inaccurate 0380 if (price.isValid()) { 0381 val *= price.rate(base.id()); 0382 } else { 0383 balanceAccurate = false; 0384 } 0385 } 0386 0387 if (shares.isNegative()) { 0388 payment += val; 0389 } else { 0390 deposit += val; 0391 } 0392 } 0393 } 0394 balance = deposit - payment; 0395 0396 ui->m_balanceLabel->setText(i18n("Balance: %1%2", 0397 balanceAccurate ? QString() : QStringLiteral("~"), 0398 balance.formatMoney(file->baseCurrency().smallestAccountFraction()))); 0399 } 0400 0401 /** 0402 * Implement common task when deleting or merging payees 0403 */ 0404 bool payeeReassign(int type) 0405 { 0406 Q_Q(KPayeesView); 0407 if (!(type >= 0 && type < KPayeeReassignDlg::TypeCount)) 0408 return false; 0409 0410 const auto file = MyMoneyFile::instance(); 0411 0412 MyMoneyFileTransaction ft; 0413 try { 0414 // create a transaction filter that contains all payees selected for removal 0415 MyMoneyTransactionFilter f = MyMoneyTransactionFilter(); 0416 const auto list = selectedPayees(); 0417 for (const auto& payee : list) { 0418 f.addPayee(payee.id()); 0419 } 0420 f.setConsiderCategorySplits(true); 0421 0422 // request a list of all transactions that still use the payees in question 0423 QList<MyMoneyTransaction> translist; 0424 file->transactionList(translist, f); 0425 // qDebug() << "[KPayeesView::slotDeletePayee] " << translist.count() << " transaction still assigned to payees"; 0426 0427 // now get a list of all schedules that make use of one of the payees 0428 QList<MyMoneySchedule> used_schedules; 0429 for (const auto& schedule : file->scheduleList()) { 0430 // loop over all splits in the transaction of the schedule 0431 const auto splits = schedule.transaction().splits(); 0432 for (const auto& split : splits) { 0433 // is the payee in the split to be deleted? 0434 if (payeeInList(list, split.payeeId())) { 0435 used_schedules.push_back(schedule); // remember this schedule 0436 break; 0437 } 0438 } 0439 } 0440 // qDebug() << "[KPayeesView::slotDeletePayee] " << used_schedules.count() << " schedules use one of the selected payees"; 0441 0442 // and a list of all loan accounts that references one of the payees 0443 QList<MyMoneyAccount> allAccounts; 0444 QList<MyMoneyAccount> usedAccounts; 0445 file->accountList(allAccounts); 0446 for (const MyMoneyAccount &account : allAccounts) { 0447 if (account.isLoan()) { 0448 MyMoneyAccountLoan loanAccount(account); 0449 for (const MyMoneyPayee &payee : list) { 0450 if (loanAccount.hasReferenceTo(payee.id())) { 0451 usedAccounts.append(account); 0452 } 0453 } 0454 } 0455 } 0456 0457 0458 MyMoneyPayee newPayee; 0459 bool addToMatchList = false; 0460 // if at least one payee is still referenced, we need to reassign its transactions first 0461 if (!translist.isEmpty() || !used_schedules.isEmpty() || !usedAccounts.isEmpty()) { 0462 0463 // first create list with all non-selected payees 0464 QList<MyMoneyPayee> remainingPayees; 0465 if (type == KPayeeReassignDlg::TypeMerge) { 0466 remainingPayees = list; 0467 } else { 0468 remainingPayees = file->payeeList(); 0469 QList<MyMoneyPayee>::iterator it_p; 0470 for (it_p = remainingPayees.begin(); it_p != remainingPayees.end();) { 0471 if (list.contains(*it_p)) { 0472 it_p = remainingPayees.erase(it_p); 0473 } else { 0474 ++it_p; 0475 } 0476 } 0477 } 0478 0479 // show error message if no payees remain 0480 if (remainingPayees.isEmpty()) { 0481 KMessageBox::error(q, i18n("At least one transaction/scheduled transaction or loan account is still referenced by a payee. " 0482 "Currently you have all payees selected. However, at least one payee must remain so " 0483 "that the transaction/scheduled transaction or loan account can be reassigned.")); 0484 return false; 0485 } 0486 0487 // show transaction reassignment dialog 0488 KPayeeReassignDlg * dlg = new KPayeeReassignDlg(static_cast<KPayeeReassignDlg::OperationType>(type), q); 0489 KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart()); 0490 QString payee_id = dlg->show(remainingPayees); 0491 addToMatchList = dlg->addToMatchList(); 0492 delete dlg; // and kill the dialog 0493 if (payee_id.isEmpty()) 0494 return false; // the user aborted the dialog, so let's abort as well 0495 0496 // try to get selected payee. If not possible and we are merging payees, 0497 // then we create a new one 0498 try { 0499 newPayee = file->payee(payee_id); 0500 } catch (const MyMoneyException &) { 0501 if (type == KPayeeReassignDlg::TypeMerge) { 0502 // it's ok to use payee_id for both arguments since the first is const, 0503 // so it's guaranteed not to change its content 0504 bool ok; 0505 std::tie(ok, payee_id) = KMyMoneyUtils::newPayee(payee_id); 0506 if (!ok) 0507 return false; // the user aborted the dialog, so let's abort as well 0508 newPayee = file->payee(payee_id); 0509 } else { 0510 return false; 0511 } 0512 } 0513 0514 // TODO : check if we have a report that explicitly uses one of our payees 0515 // and issue an appropriate warning 0516 try { 0517 // now loop over all transactions and reassign payee 0518 for (auto& transaction : translist) { 0519 // create a copy of the splits list in the transaction 0520 // loop over all splits 0521 for (auto& split : transaction.splits()) { 0522 // if the split is assigned to one of the selected payees, we need to modify it 0523 if (split.isMatched()) { 0524 auto tm = split.matchedTransaction(); 0525 for (auto& sm : tm.splits()) { 0526 if (payeeInList(list, sm.payeeId())) { 0527 sm.setPayeeId(payee_id); // first modify payee in current split 0528 // then modify the split in our local copy of the transaction list 0529 tm.modifySplit(sm); // this does not modify the list object 'splits'! 0530 } 0531 } 0532 split.addMatch(tm); 0533 transaction.modifySplit(split); // this does not modify the list object 'splits'! 0534 } 0535 if (payeeInList(list, split.payeeId())) { 0536 split.setPayeeId(payee_id); // first modify payee in current split 0537 // then modify the split in our local copy of the transaction list 0538 transaction.modifySplit(split); // this does not modify the list object 'splits'! 0539 } 0540 } // for - Splits 0541 file->modifyTransaction(transaction); // modify the transaction in the MyMoney object 0542 } // for - Transactions 0543 0544 // now loop over all schedules and reassign payees 0545 for (auto& schedule : used_schedules) { 0546 // create copy of transaction in current schedule 0547 auto trans = schedule.transaction(); 0548 // create copy of lists of splits 0549 for (auto& split : trans.splits()) { 0550 if (payeeInList(list, split.payeeId())) { 0551 split.setPayeeId(payee_id); 0552 trans.modifySplit(split); // does not modify the list object 'splits'! 0553 } 0554 } // for - Splits 0555 // store transaction in current schedule 0556 schedule.setTransaction(trans); 0557 file->modifySchedule(schedule); // modify the schedule in the MyMoney engine 0558 } // for - Schedules 0559 0560 // reassign the payees in the loans that reference the deleted payees 0561 for (const MyMoneyAccount& account : qAsConst(usedAccounts)) { 0562 MyMoneyAccountLoan loanAccount(account); 0563 loanAccount.setPayee(payee_id); 0564 file->modifyAccount(loanAccount); 0565 } 0566 0567 } catch (const MyMoneyException &e) { 0568 KMessageBox::detailedError(q, i18n("Unable to reassign payee of transaction/split"), e.what()); 0569 } 0570 } else { // if !translist.isEmpty() 0571 if (type == KPayeeReassignDlg::TypeMerge) { 0572 KMessageBox::error(q, i18n("Nothing to merge."), i18n("Merge Payees")); 0573 return false; 0574 } 0575 } 0576 0577 bool ignorecase; 0578 QStringList payeeNames; 0579 auto matchType = newPayee.matchData(ignorecase, payeeNames); 0580 QStringList deletedMatchPattern; 0581 0582 // now loop over all selected payees and remove them 0583 for (QList<MyMoneyPayee>::const_iterator it = list.constBegin(); it != list.constEnd(); ++it) { 0584 if (newPayee.id() != (*it).id()) { 0585 if (addToMatchList) { 0586 QStringList matchPattern; 0587 auto mType = (*it).matchData(ignorecase, matchPattern); 0588 switch (mType) { 0589 case eMyMoney::Payee::MatchType::NameExact: 0590 matchPattern << QStringLiteral("^%1$").arg((*it).name()); 0591 break; 0592 case eMyMoney::Payee::MatchType::Name: 0593 matchPattern << (*it).name(); 0594 break; 0595 default: 0596 break; 0597 } 0598 if (!matchPattern.isEmpty()) { 0599 deletedMatchPattern << matchPattern; 0600 } 0601 } 0602 file->removePayee(*it); 0603 } 0604 } 0605 0606 // if we initially have no matching turned on, we just ignore the case (default) 0607 if (matchType == eMyMoney::Payee::MatchType::Disabled) 0608 ignorecase = true; 0609 0610 // update the destination payee if this was requested by the user 0611 if (addToMatchList && deletedMatchPattern.count() > 0) { 0612 // add new names to the list 0613 // TODO: it would be cool to somehow shrink the list to make better use 0614 // of regular expressions at this point. For now, we leave this task 0615 // to the user himeself. 0616 0617 // the deletedMatchPattern contains entries of MatchType::Key. Let's 0618 // check if the destination entry needs to be converted to this formatMoney 0619 switch (matchType) { 0620 case eMyMoney::Payee::MatchType::Disabled: 0621 case eMyMoney::Payee::MatchType::Key: 0622 break; 0623 case eMyMoney::Payee::MatchType::Name: 0624 payeeNames.clear(); 0625 payeeNames << newPayee.name(); 0626 break; 0627 case eMyMoney::Payee::MatchType::NameExact: 0628 payeeNames.clear(); 0629 payeeNames << QStringLiteral("^%1$").arg(newPayee.name()); 0630 break; 0631 } 0632 0633 QStringList::const_iterator it_n; 0634 for (it_n = deletedMatchPattern.constBegin(); it_n != deletedMatchPattern.constEnd(); ++it_n) { 0635 // make sure we really need it and it is not caught by an existing regexp 0636 QStringList::const_iterator it_k; 0637 for (it_k = payeeNames.constBegin(); it_k != payeeNames.constEnd(); ++it_k) { 0638 const QRegularExpression exp(*it_k, ignorecase ? QRegularExpression::CaseInsensitiveOption : QRegularExpression::NoPatternOption); 0639 const auto payee(exp.match(*it_n)); 0640 if (payee.hasMatch()) 0641 break; 0642 } 0643 if (it_k == payeeNames.constEnd()) 0644 payeeNames << *it_n; 0645 } 0646 0647 // and update the payee in the engine context 0648 // make sure to turn on matching for this payee in the right mode 0649 newPayee.setMatchData(eMyMoney::Payee::MatchType::Key, ignorecase, payeeNames); 0650 file->modifyPayee(newPayee); 0651 } 0652 ft.commit(); 0653 0654 } catch (const MyMoneyException &e) { 0655 KMessageBox::detailedError(q, i18n("Unable to remove payee(s)"), e.what()); 0656 } 0657 0658 return true; 0659 } 0660 0661 /** 0662 * Check if a list contains a payee with a given id 0663 * 0664 * @param list const reference to value list 0665 * @param id const reference to id 0666 * 0667 * @retval true object has been found 0668 * @retval false object is not in list 0669 */ 0670 bool payeeInList(const QList<MyMoneyPayee>& list, const QString& id) const 0671 { 0672 for (const auto& payee : list) { 0673 if (payee.id() == id) { 0674 return true; 0675 } 0676 } 0677 return false; 0678 } 0679 0680 /** Checks whether the currently selected payee is "dirty" 0681 * @return true, if payee is modified (is "dirty"); false otherwise 0682 */ 0683 bool isDirty() const 0684 { 0685 return ui->m_updateButton->isEnabled(); 0686 } 0687 0688 /** Sets the payee's "dirty" (modified) status 0689 * @param dirty if true (default), payee will be set to dirty 0690 */ 0691 void setDirty(bool dirty) 0692 { 0693 ui->m_updateButton->setEnabled(dirty); 0694 } 0695 0696 void selectPayeeAndTransaction(const QString& payeeId, const QString& accountId = QString(), const QString& journalEntryId = QString()) 0697 { 0698 Q_Q(KPayeesView); 0699 if (!q->isVisible()) 0700 return; 0701 0702 const auto file = MyMoneyFile::instance(); 0703 QModelIndex idx; 0704 QModelIndexList list; 0705 do { 0706 list = ui->m_payees->model()->match(ui->m_payees->model()->index(0, 0), 0707 eMyMoney::Model::IdRole, 0708 payeeId, 0709 -1, // all splits 0710 Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive)); 0711 0712 if (list.isEmpty()) { 0713 if (!ui->m_searchWidget->text().isEmpty()) { 0714 ui->m_searchWidget->clear(); 0715 continue; 0716 } 0717 if (m_renameProxyModel->referenceFilter() != ItemRenameProxyModel::eAllItem) { 0718 m_renameProxyModel->setReferenceFilter(ItemRenameProxyModel::eAllItem); 0719 continue; 0720 } 0721 } 0722 break; 0723 } while (true); 0724 0725 if (list.isEmpty()) { 0726 qDebug() << "selectPayeeAndTransaction: Payee not found" << payeeId; 0727 return; 0728 } 0729 0730 m_selections.clearSelections(); 0731 m_selections.setSelection(SelectedObjects::Payee, payeeId); 0732 0733 // select the payee 0734 ui->m_payees->setCurrentIndex(list.at(0)); 0735 0736 const auto model = ui->m_register->model(); 0737 Q_ASSERT(model != nullptr); 0738 0739 const auto rows = model->rowCount(); 0740 int transactionRow = -1; 0741 0742 // it could be, that the selected JournalEntryId is not available here 0743 // so we also scan for the transactionId 0744 0745 const auto journalIdx = file->journalModel()->indexById(journalEntryId); 0746 const auto transactionId = journalIdx.data(eMyMoney::Model::JournalTransactionIdRole).toString(); 0747 0748 // scan all entries shown for this payee 0749 for (int row = 0; row < rows; ++row) { 0750 idx = model->index(row, 0); 0751 if (idx.data(eMyMoney::Model::IdRole).toString() == journalEntryId) { 0752 transactionRow = row; 0753 break; 0754 0755 } else if (idx.data(eMyMoney::Model::JournalTransactionIdRole).toString() == transactionId) { 0756 // if the transaction id matches and we don't have a match already we keep it 0757 if (transactionRow == -1) 0758 transactionRow = row; 0759 // in case the account is available and it matches, we use it right away 0760 if (!accountId.isEmpty() && (idx.data(eMyMoney::Model::SplitAccountIdRole).toString() == accountId)) { 0761 transactionRow = row; 0762 break; 0763 } 0764 } else if (transactionRow != -1) { 0765 // if we have a match for a transaction, we use it 0766 break; 0767 } 0768 } 0769 0770 ensurePayeeVisible(payeeId); 0771 0772 if (transactionRow != -1) { 0773 // use the date column here for the index so that scrollTo has an effect 0774 // because column 0 (Number) is hidden in the payees view. 0775 idx = model->index(transactionRow, JournalModel::Column::Date); 0776 ui->m_register->setCurrentIndex(idx); 0777 0778 m_selections.setSelection(SelectedObjects::Account, idx.data(eMyMoney::Model::SplitAccountIdRole).toString()); 0779 m_selections.setSelection(SelectedObjects::JournalEntry, idx.data(eMyMoney::Model::IdRole).toString()); 0780 0781 // if it's not the last transaction, we scroll a bit further 0782 if (transactionRow + 1 < rows) { 0783 idx = model->index(transactionRow + 1, JournalModel::Column::Date); 0784 } 0785 ui->m_register->scrollTo(idx); 0786 } 0787 0788 q->Q_EMIT requestSelectionChange(m_selections); 0789 } 0790 0791 void finalizePendingChanges() 0792 { 0793 Q_Q(KPayeesView); 0794 if (isDirty()) { 0795 if (KMessageBox::questionTwoActions(q, 0796 i18n("<qt>Do you want to save the changes for <b>%1</b>?</qt>", m_newName), 0797 i18n("Save changes"), 0798 KMMYesNo::yes(), 0799 KMMYesNo::no()) 0800 == KMessageBox::PrimaryAction) { 0801 q->slotUpdatePayee(); 0802 } 0803 } 0804 } 0805 0806 void selectPayee() 0807 { 0808 Q_Q(KPayeesView); 0809 const auto selectedPayeesList = selectedPayees(); 0810 m_payee = MyMoneyPayee(); 0811 if (!selectedPayeesList.isEmpty()) { 0812 m_payee = selectedPayeesList.at(0); 0813 } 0814 0815 // as of now we are updating only the last selected payee, and until 0816 // selection mode of the QListView has been changed to Extended, this 0817 // will also be the only selection and behave exactly as before - Andreas 0818 try { 0819 m_newName = m_payee.name(); 0820 loadDetails(); 0821 0822 q->slotPayeeDataChanged(); 0823 showTransactions(); 0824 0825 } catch (const MyMoneyException& e) { 0826 qDebug() << "exception during display of payee: " << e.what(); 0827 // clear display data 0828 m_transactionFilter->setPayeeIdList(QStringList()); 0829 m_payee = MyMoneyPayee(); 0830 } 0831 0832 m_selections.setSelection(SelectedObjects::Payee, selectedPayeeIds()); 0833 q->Q_EMIT requestSelectionChange(m_selections); 0834 } 0835 0836 Ui::KPayeesView* ui; 0837 LedgerPayeeFilter* m_transactionFilter; 0838 0839 MyMoneyPayee m_payee; 0840 QString m_newName; 0841 MyMoneyContact* m_contact; 0842 int m_syncedPayees; 0843 QList<MyMoneyPayee> m_payeesToSync; 0844 0845 AccountNamesFilterProxyModel *m_filterProxyModel; 0846 ItemRenameProxyModel* m_renameProxyModel; 0847 }; 0848 0849 #endif