File indexing completed on 2024-05-12 16:43:50
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 #include "kpayeesview.h" 0017 0018 // ---------------------------------------------------------------------------- 0019 // QT Includes 0020 0021 #include <QMap> 0022 #include <QList> 0023 #include <QTimer> 0024 #include <QDesktopServices> 0025 #include <QIcon> 0026 0027 // ---------------------------------------------------------------------------- 0028 // KDE Includes 0029 0030 #include <KLocalizedString> 0031 #include <KMessageBox> 0032 #include <KHelpClient> 0033 #include <KSharedConfig> 0034 #include <KListWidgetSearchLine> 0035 0036 // ---------------------------------------------------------------------------- 0037 // Project Includes 0038 0039 #include <config-kmymoney.h> 0040 #include "ui_kpayeesview.h" 0041 #include "kmymoneyviewbase_p.h" 0042 #include "kmymoneyutils.h" 0043 #include "kmymoneymvccombo.h" 0044 #include "mymoneypayee.h" 0045 #include "mymoneyexception.h" 0046 #include "mymoneyfile.h" 0047 #include "mymoneyaccount.h" 0048 #include "mymoneymoney.h" 0049 #include "mymoneytransactionfilter.h" 0050 #include "kmymoneysettings.h" 0051 #include "kpayeereassigndlg.h" 0052 #include "models.h" 0053 #include "accountsmodel.h" 0054 #include "mymoneysecurity.h" 0055 #include "mymoneyschedule.h" 0056 #include "mymoneycontact.h" 0057 #include "mymoneyaccountloan.h" 0058 #include "mymoneysplit.h" 0059 #include "mymoneyprice.h" 0060 #include "mymoneytransaction.h" 0061 #include "icons/icons.h" 0062 #include "transaction.h" 0063 #include "widgetenums.h" 0064 #include "mymoneyenums.h" 0065 #include "modelenums.h" 0066 #include "menuenums.h" 0067 0068 using namespace Icons; 0069 0070 // *** KPayeeListItem Implementation *** 0071 0072 class KPayeeListItem : public QListWidgetItem 0073 { 0074 public: 0075 /** 0076 * Constructor to be used to construct a payee entry object. 0077 * 0078 * @param parent pointer to the QListWidget object this entry should be 0079 * added to. 0080 * @param payee const reference to MyMoneyPayee for which 0081 * the QListWidget entry is constructed 0082 */ 0083 explicit KPayeeListItem(QListWidget *parent, const MyMoneyPayee& payee) : 0084 QListWidgetItem(parent, QListWidgetItem::UserType), 0085 m_payee(payee) 0086 { 0087 setText(payee.name()); 0088 // allow in column rename 0089 setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); 0090 } 0091 0092 ~KPayeeListItem() 0093 { 0094 } 0095 0096 const MyMoneyPayee& payee() const { 0097 return m_payee; 0098 } 0099 0100 private: 0101 MyMoneyPayee m_payee; 0102 }; 0103 0104 enum filterTypeE { eAllPayees = 0, eReferencedPayees = 1, eUnusedPayees = 2 }; 0105 0106 class KPayeesViewPrivate : public KMyMoneyViewBasePrivate 0107 { 0108 Q_DECLARE_PUBLIC(KPayeesView) 0109 0110 public: 0111 explicit KPayeesViewPrivate(KPayeesView *qq) : 0112 KMyMoneyViewBasePrivate(), 0113 q_ptr(qq), 0114 ui(new Ui::KPayeesView), 0115 m_contact(nullptr), 0116 m_payeeRow(0), 0117 m_needLoad(true), 0118 m_searchWidget(nullptr), 0119 m_inSelection(false), 0120 m_allowEditing(true), 0121 m_payeeFilterType(0), 0122 m_filterProxyModel(nullptr) 0123 { 0124 } 0125 0126 ~KPayeesViewPrivate() 0127 { 0128 if(!m_needLoad) { 0129 // remember the splitter settings for startup 0130 auto grp = KSharedConfig::openConfig()->group("Last Use Settings"); 0131 grp.writeEntry("KPayeesViewSplitterSize", ui->m_splitter->saveState()); 0132 grp.sync(); 0133 } 0134 delete ui; 0135 } 0136 0137 void init() 0138 { 0139 Q_Q(KPayeesView); 0140 m_needLoad = false; 0141 ui->setupUi(q); 0142 0143 m_contact = new MyMoneyContact(q); 0144 m_filterProxyModel = new AccountNamesFilterProxyModel(q); 0145 m_filterProxyModel->setHideEquityAccounts(!KMyMoneySettings::expertMode()); 0146 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}); 0147 0148 auto const model = Models::instance()->accountsModel(); 0149 m_filterProxyModel->setSourceColumns(model->getColumns()); 0150 m_filterProxyModel->setSourceModel(model); 0151 m_filterProxyModel->sort((int)eAccountsModel::Column::Account); 0152 ui->comboDefaultCategory->setModel(m_filterProxyModel); 0153 0154 ui->matchTypeCombo->addItem(i18nc("@item No matching", "No matching"), static_cast<int>(eMyMoney::Payee::MatchType::Disabled)); 0155 ui->matchTypeCombo->addItem(i18nc("@item Match Payees name partially", "Match Payees name (partial)"), static_cast<int>(eMyMoney::Payee::MatchType::Name)); 0156 ui->matchTypeCombo->addItem(i18nc("@item Match Payees name exactly", "Match Payees name (exact)"), static_cast<int>(eMyMoney::Payee::MatchType::NameExact)); 0157 ui->matchTypeCombo->addItem(i18nc("@item Search match in list", "Match on a name listed below"), static_cast<int>(eMyMoney::Payee::MatchType::Key)); 0158 0159 // create the searchline widget 0160 // and insert it into the existing layout 0161 m_searchWidget = new KListWidgetSearchLine(q, ui->m_payeesList); 0162 m_searchWidget->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); 0163 ui->m_payeesList->setContextMenuPolicy(Qt::CustomContextMenu); 0164 ui->m_listTopHLayout->insertWidget(0, m_searchWidget); 0165 0166 //load the filter type 0167 ui->m_filterBox->addItem(i18nc("@item Show all payees", "All")); 0168 ui->m_filterBox->addItem(i18nc("@item Show only used payees", "Used")); 0169 ui->m_filterBox->addItem(i18nc("@item Show only unused payees", "Unused")); 0170 ui->m_filterBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); 0171 0172 ui->m_newButton->setIcon(Icons::get(Icon::ListAddUser)); 0173 ui->m_renameButton->setIcon(Icons::get(Icon::UserProperties)); 0174 ui->m_deleteButton->setIcon(Icons::get(Icon::ListRemoveUser)); 0175 ui->m_mergeButton->setIcon(Icons::get(Icon::Merge)); 0176 ui->m_updateButton->setIcon(Icons::get(Icon::DialogOK)); 0177 ui->m_syncAddressbook->setIcon(Icons::get(Icon::Refresh)); 0178 ui->m_sendMail->setIcon(Icons::get(Icon::MailMessage)); 0179 0180 ui->m_updateButton->setEnabled(false); 0181 ui->m_syncAddressbook->setEnabled(false); 0182 #ifndef ENABLE_ADDRESSBOOK 0183 ui->m_syncAddressbook->hide(); 0184 #endif 0185 ui->matchTypeCombo->setCurrentIndex(0); 0186 0187 ui->checkMatchIgnoreCase->setEnabled(false); 0188 0189 ui->checkEnableDefaultCategory->setChecked(false); 0190 ui->labelDefaultCategory->setEnabled(false); 0191 ui->comboDefaultCategory->setEnabled(false); 0192 0193 QList<eWidgets::eTransaction::Column> cols { 0194 eWidgets::eTransaction::Column::Date, 0195 eWidgets::eTransaction::Column::Account, 0196 eWidgets::eTransaction::Column::Detail, 0197 eWidgets::eTransaction::Column::ReconcileFlag, 0198 eWidgets::eTransaction::Column::Payment, 0199 eWidgets::eTransaction::Column::Deposit}; 0200 ui->m_register->setupRegister(MyMoneyAccount(), cols); 0201 ui->m_register->setSelectionMode(QTableWidget::SingleSelection); 0202 ui->m_register->setDetailsColumnType(eWidgets::eRegister::DetailColumn::AccountFirst); 0203 ui->m_balanceLabel->hide(); 0204 0205 q->connect(m_contact, &MyMoneyContact::contactFetched, q, &KPayeesView::slotContactFetched); 0206 0207 q->connect(ui->m_payeesList, static_cast<void (QListWidget::*)(QListWidgetItem*, QListWidgetItem*)>(&QListWidget::currentItemChanged), q, static_cast<void (KPayeesView::*)(QListWidgetItem*, QListWidgetItem*)>(&KPayeesView::slotSelectPayee)); 0208 q->connect(ui->m_payeesList, &QListWidget::itemSelectionChanged, q, static_cast<void (KPayeesView::*)()>(&KPayeesView::slotSelectPayee)); 0209 q->connect(ui->m_payeesList, &QListWidget::itemDoubleClicked, q, &KPayeesView::slotStartRename); 0210 q->connect(ui->m_payeesList, &QListWidget::itemChanged, q, &KPayeesView::slotRenameSinglePayee); 0211 q->connect(ui->m_payeesList, &QWidget::customContextMenuRequested, q, &KPayeesView::slotShowPayeesMenu); 0212 0213 q->connect(ui->m_newButton, &QAbstractButton::clicked, q, &KPayeesView::slotNewPayee); 0214 q->connect(ui->m_renameButton, &QAbstractButton::clicked, q, &KPayeesView::slotRenamePayee); 0215 q->connect(ui->m_deleteButton, &QAbstractButton::clicked, q, &KPayeesView::slotDeletePayee); 0216 q->connect(ui->m_mergeButton, &QAbstractButton::clicked, q, &KPayeesView::slotMergePayee); 0217 0218 q->connect(ui->addressEdit, &QTextEdit::textChanged, q, &KPayeesView::slotPayeeDataChanged); 0219 q->connect(ui->payeecityEdit, &QLineEdit::textChanged, q, &KPayeesView::slotPayeeDataChanged); 0220 q->connect(ui->payeestateEdit, &QLineEdit::textChanged, q, &KPayeesView::slotPayeeDataChanged); 0221 q->connect(ui->postcodeEdit, &QLineEdit::textChanged, q, &KPayeesView::slotPayeeDataChanged); 0222 q->connect(ui->telephoneEdit, &QLineEdit::textChanged, q, &KPayeesView::slotPayeeDataChanged); 0223 q->connect(ui->emailEdit, &QLineEdit::textChanged, q, &KPayeesView::slotPayeeDataChanged); 0224 q->connect(ui->notesEdit, &QTextEdit::textChanged, q, &KPayeesView::slotPayeeDataChanged); 0225 q->connect(ui->matchKeyEditList, &KEditListWidget::changed, q, &KPayeesView::slotKeyListChanged); 0226 0227 q->connect(ui->matchTypeCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), q, &KPayeesView::slotPayeeDataChanged); 0228 q->connect(ui->checkMatchIgnoreCase, &QAbstractButton::toggled, q, &KPayeesView::slotPayeeDataChanged); 0229 0230 q->connect(ui->checkEnableDefaultCategory, &QAbstractButton::toggled, q, &KPayeesView::slotPayeeDataChanged); 0231 q->connect(ui->comboDefaultCategory, &KMyMoneyAccountCombo::accountSelected, q, &KPayeesView::slotPayeeDataChanged); 0232 q->connect(ui->buttonSuggestACategory, &QAbstractButton::clicked, q, &KPayeesView::slotChooseDefaultAccount); 0233 0234 q->connect(ui->m_updateButton, &QAbstractButton::clicked, q, &KPayeesView::slotUpdatePayee); 0235 q->connect(ui->m_syncAddressbook, &QAbstractButton::clicked, q, &KPayeesView::slotSyncAddressBook); 0236 q->connect(ui->m_helpButton, &QAbstractButton::clicked, q, &KPayeesView::slotHelp); 0237 q->connect(ui->m_sendMail, &QAbstractButton::clicked, q, &KPayeesView::slotSendMail); 0238 0239 q->connect(ui->m_register, &KMyMoneyRegister::Register::editTransaction, q, &KPayeesView::slotSelectTransaction); 0240 0241 q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KPayeesView::refresh); 0242 0243 q->connect(ui->m_filterBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), q, &KPayeesView::slotChangeFilter); 0244 0245 q->connect(ui->payeeIdentifiers, &KPayeeIdentifierView::dataChanged, q, &KPayeesView::slotPayeeDataChanged); 0246 0247 // use the size settings of the last run (if any) 0248 KConfigGroup grp = KSharedConfig::openConfig()->group("Last Use Settings"); 0249 ui->m_splitter->restoreState(grp.readEntry("KPayeesViewSplitterSize", QByteArray())); 0250 ui->m_splitter->setChildrenCollapsible(false); 0251 0252 //At start we haven't any payee selected 0253 ui->m_tabWidget->setEnabled(false); // disable tab widget 0254 ui->m_deleteButton->setEnabled(false); //disable delete, rename and merge buttons 0255 ui->m_renameButton->setEnabled(false); 0256 ui->m_mergeButton->setEnabled(false); 0257 m_payee = MyMoneyPayee(); // make sure we don't access an undefined payee 0258 clearItemData(); 0259 } 0260 0261 void loadPayees() 0262 { 0263 Q_Q(KPayeesView); 0264 if (m_inSelection) 0265 return; 0266 0267 QMap<QString, bool> isSelected; 0268 QString id; 0269 MyMoneyFile* file = MyMoneyFile::instance(); 0270 0271 // remember which items are selected in the list 0272 QList<QListWidgetItem *> selectedItems = ui->m_payeesList->selectedItems(); 0273 QList<QListWidgetItem *>::const_iterator payeesIt = selectedItems.constBegin(); 0274 0275 while (payeesIt != selectedItems.constEnd()) { 0276 KPayeeListItem* item = dynamic_cast<KPayeeListItem*>(*payeesIt); 0277 if (item) 0278 isSelected[item->payee().id()] = true; 0279 ++payeesIt; 0280 } 0281 0282 // keep current selected item 0283 KPayeeListItem *currentItem = static_cast<KPayeeListItem *>(ui->m_payeesList->currentItem()); 0284 if (currentItem) 0285 id = currentItem->payee().id(); 0286 0287 m_allowEditing = false; 0288 // clear the list 0289 m_searchWidget->clear(); 0290 m_searchWidget->updateSearch(); 0291 ui->m_payeesList->clear(); 0292 ui->m_register->clear(); 0293 currentItem = 0; 0294 0295 QList<MyMoneyPayee>list = file->payeeList(); 0296 QList<MyMoneyPayee>::ConstIterator it; 0297 0298 for (it = list.constBegin(); it != list.constEnd(); ++it) { 0299 if (m_payeeFilterType == eAllPayees || 0300 (m_payeeFilterType == eReferencedPayees && file->isReferenced(*it)) || 0301 (m_payeeFilterType == eUnusedPayees && !file->isReferenced(*it))) { 0302 KPayeeListItem* item = new KPayeeListItem(ui->m_payeesList, *it); 0303 if (item->payee().id() == id) 0304 currentItem = item; 0305 if (isSelected[item->payee().id()]) 0306 item->setSelected(true); 0307 } 0308 } 0309 ui->m_payeesList->sortItems(); 0310 0311 if (currentItem) { 0312 ui->m_payeesList->setCurrentItem(currentItem); 0313 ui->m_payeesList->scrollToItem(currentItem); 0314 } 0315 0316 m_filterProxyModel->invalidate(); 0317 ui->comboDefaultCategory->expandAll(); 0318 0319 q->slotSelectPayee(0, 0); 0320 m_allowEditing = true; 0321 } 0322 0323 void selectedPayees(QList<MyMoneyPayee>& payeesList) const 0324 { 0325 QList<QListWidgetItem *> selectedItems = ui->m_payeesList->selectedItems(); 0326 QList<QListWidgetItem *>::ConstIterator itemsIt = selectedItems.constBegin(); 0327 while (itemsIt != selectedItems.constEnd()) { 0328 KPayeeListItem* item = dynamic_cast<KPayeeListItem*>(*itemsIt); 0329 if (item) 0330 payeesList << item->payee(); 0331 ++itemsIt; 0332 } 0333 } 0334 0335 void ensurePayeeVisible(const QString& id) 0336 { 0337 for (int i = 0; i < ui->m_payeesList->count(); ++i) { 0338 KPayeeListItem* p = dynamic_cast<KPayeeListItem*>(ui->m_payeesList->item(0)); 0339 if (p && p->payee().id() == id) { 0340 ui->m_payeesList->scrollToItem(p, QAbstractItemView::PositionAtCenter); 0341 0342 ui->m_payeesList->setCurrentItem(p); // active item and deselect all others 0343 ui->m_payeesList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it 0344 break; 0345 } 0346 } 0347 } 0348 0349 void clearItemData() 0350 { 0351 ui->addressEdit->setText(QString()); 0352 ui->payeecityEdit->setText(QString()); 0353 ui->payeestateEdit->setText(QString()); 0354 ui->postcodeEdit->setText(QString()); 0355 ui->telephoneEdit->setText(QString()); 0356 ui->emailEdit->setText(QString()); 0357 ui->notesEdit->setText(QString()); 0358 showTransactions(); 0359 } 0360 0361 /** 0362 * This method loads the m_transactionList, clears 0363 * the m_TransactionPtrVector and rebuilds and sorts 0364 * it according to the current settings. Then it 0365 * loads the m_transactionView with the transaction data. 0366 */ 0367 void showTransactions() 0368 { 0369 MyMoneyMoney balance; 0370 const auto file = MyMoneyFile::instance(); 0371 MyMoneySecurity base = file->baseCurrency(); 0372 0373 // setup sort order 0374 ui->m_register->setSortOrder(KMyMoneySettings::sortSearchView()); 0375 0376 // clear the register 0377 ui->m_register->clear(); 0378 0379 if (m_selectedPayeesList.isEmpty() || !ui->m_tabWidget->isEnabled()) { 0380 ui->m_balanceLabel->setText(i18n("Balance: %1", balance.formatMoney(file->baseCurrency().smallestAccountFraction()))); 0381 return; 0382 } 0383 0384 // setup the list and the pointer vector 0385 MyMoneyTransactionFilter filter; 0386 0387 for (QList<MyMoneyPayee>::const_iterator it = m_selectedPayeesList.constBegin(); 0388 it != m_selectedPayeesList.constEnd(); 0389 ++it) 0390 filter.addPayee((*it).id()); 0391 0392 filter.setDateFilter(KMyMoneySettings::startDate().date(), QDate()); 0393 filter.setConsiderCategorySplits(true); 0394 0395 // retrieve the list from the engine 0396 file->transactionList(m_transactionList, filter); 0397 0398 // create the elements for the register 0399 QList<QPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator it; 0400 QMap<QString, int> uniqueMap; 0401 MyMoneyMoney deposit, payment; 0402 0403 int splitCount = 0; 0404 bool balanceAccurate = true; 0405 for (it = m_transactionList.constBegin(); it != m_transactionList.constEnd(); ++it) { 0406 const MyMoneySplit& split = (*it).second; 0407 MyMoneyAccount acc = file->account(split.accountId()); 0408 if (!acc.isIncomeExpense()) { 0409 ++splitCount; 0410 uniqueMap[(*it).first.id()]++; 0411 0412 KMyMoneyRegister::Register::transactionFactory(ui->m_register, (*it).first, (*it).second, uniqueMap[(*it).first.id()]); 0413 0414 // take care of foreign currencies 0415 MyMoneyMoney val = split.shares().abs(); 0416 if (acc.currencyId() != base.id()) { 0417 const MyMoneyPrice& price = file->price(acc.currencyId(), base.id()); 0418 // in case the price is valid, we use it. Otherwise, we keep 0419 // a flag that tells us that the balance is somewhat inaccurate 0420 if (price.isValid()) { 0421 val *= price.rate(base.id()); 0422 } else { 0423 balanceAccurate = false; 0424 } 0425 } 0426 0427 if (split.shares().isNegative()) { 0428 payment += val; 0429 } else { 0430 deposit += val; 0431 } 0432 } 0433 } 0434 balance = deposit - payment; 0435 0436 // add the group markers 0437 ui->m_register->addGroupMarkers(); 0438 0439 // sort the transactions according to the sort setting 0440 ui->m_register->sortItems(); 0441 0442 // remove trailing and adjacent markers 0443 ui->m_register->removeUnwantedGroupMarkers(); 0444 0445 ui->m_register->updateRegister(true); 0446 0447 // we might end up here with updates disabled on the register so 0448 // make sure that we enable updates here 0449 ui->m_register->setUpdatesEnabled(true); 0450 ui->m_balanceLabel->setText(i18n("Balance: %1%2", 0451 balanceAccurate ? "" : "~", 0452 balance.formatMoney(file->baseCurrency().smallestAccountFraction()))); 0453 } 0454 0455 /** 0456 * Implement common task when deleting or merging payees 0457 */ 0458 bool payeeReassign(int type) 0459 { 0460 Q_Q(KPayeesView); 0461 if (!(type >= 0 && type < KPayeeReassignDlg::TypeCount)) 0462 return false; 0463 0464 auto addPatternOrName = [&](const QString& pattern) { 0465 if (pattern.startsWith(QLatin1String("^")) && pattern.startsWith(QLatin1String("^"))) { 0466 return pattern; 0467 } 0468 return QRegularExpression::escape(pattern); 0469 }; 0470 0471 const auto file = MyMoneyFile::instance(); 0472 0473 MyMoneyFileTransaction ft; 0474 try { 0475 // create a transaction filter that contains all payees selected for removal 0476 MyMoneyTransactionFilter f = MyMoneyTransactionFilter(); 0477 for (QList<MyMoneyPayee>::const_iterator it = m_selectedPayeesList.constBegin(); 0478 it != m_selectedPayeesList.constEnd(); ++it) { 0479 f.addPayee((*it).id()); 0480 } 0481 f.setConsiderCategorySplits(true); 0482 // request a list of all transactions that still use the payees in question 0483 QList<MyMoneyTransaction> translist = file->transactionList(f); 0484 // qDebug() << "[KPayeesView::slotDeletePayee] " << translist.count() << " transaction still assigned to payees"; 0485 0486 // now get a list of all schedules that make use of one of the payees 0487 QList<MyMoneySchedule> used_schedules; 0488 foreach (const auto schedule, file->scheduleList()) { 0489 // loop over all splits in the transaction of the schedule 0490 foreach (const auto split, schedule.transaction().splits()) { 0491 // is the payee in the split to be deleted? 0492 if (payeeInList(m_selectedPayeesList, split.payeeId())) { 0493 used_schedules.push_back(schedule); // remember this schedule 0494 break; 0495 } 0496 } 0497 } 0498 // qDebug() << "[KPayeesView::slotDeletePayee] " << used_schedules.count() << " schedules use one of the selected payees"; 0499 0500 // and a list of all loan accounts that references one of the payees 0501 QList<MyMoneyAccount> allAccounts; 0502 QList<MyMoneyAccount> usedAccounts; 0503 file->accountList(allAccounts); 0504 foreach (const MyMoneyAccount &account, allAccounts) { 0505 if (account.isLoan()) { 0506 MyMoneyAccountLoan loanAccount(account); 0507 foreach (const MyMoneyPayee &payee, m_selectedPayeesList) { 0508 if (loanAccount.hasReferenceTo(payee.id())) { 0509 usedAccounts.append(account); 0510 } 0511 } 0512 } 0513 } 0514 0515 0516 MyMoneyPayee newPayee; 0517 bool addToMatchList = false; 0518 // if at least one payee is still referenced, we need to reassign its transactions first 0519 if (!translist.isEmpty() || !used_schedules.isEmpty() || !usedAccounts.isEmpty()) { 0520 0521 // first create list with all non-selected payees 0522 QList<MyMoneyPayee> remainingPayees; 0523 if (type == KPayeeReassignDlg::TypeMerge) { 0524 remainingPayees = m_selectedPayeesList; 0525 } else { 0526 remainingPayees = file->payeeList(); 0527 QList<MyMoneyPayee>::iterator it_p; 0528 for (it_p = remainingPayees.begin(); it_p != remainingPayees.end();) { 0529 if (m_selectedPayeesList.contains(*it_p)) { 0530 it_p = remainingPayees.erase(it_p); 0531 } else { 0532 ++it_p; 0533 } 0534 } 0535 } 0536 0537 // show error message if no payees remain 0538 if (remainingPayees.isEmpty()) { 0539 KMessageBox::sorry(q, i18n("At least one transaction/scheduled transaction or loan account is still referenced by a payee. " 0540 "Currently you have all payees selected. However, at least one payee must remain so " 0541 "that the transaction/scheduled transaction or loan account can be reassigned.")); 0542 return false; 0543 } 0544 0545 // show transaction reassignment dialog 0546 KPayeeReassignDlg * dlg = new KPayeeReassignDlg(static_cast<KPayeeReassignDlg::OperationType>(type), q); 0547 KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart()); 0548 QString payee_id = dlg->show(remainingPayees); 0549 addToMatchList = dlg->addToMatchList(); 0550 delete dlg; // and kill the dialog 0551 if (payee_id.isEmpty()) 0552 return false; // the user aborted the dialog, so let's abort as well 0553 0554 // try to get selected payee. If not possible and we are merging payees, 0555 // then we create a new one 0556 try { 0557 newPayee = file->payee(payee_id); 0558 } catch (const MyMoneyException &) { 0559 if (type == KPayeeReassignDlg::TypeMerge) { 0560 // it's ok to use payee_id for both arguments since the first is const, 0561 // so it's guaranteed not to change its content 0562 if (!KMyMoneyUtils::newPayee(payee_id, payee_id)) 0563 return false; // the user aborted the dialog, so let's abort as well 0564 newPayee = file->payee(payee_id); 0565 } else { 0566 return false; 0567 } 0568 } 0569 0570 // TODO : check if we have a report that explicitly uses one of our payees 0571 // and issue an appropriate warning 0572 try { 0573 // now loop over all transactions and reassign payee 0574 for (auto& transaction : translist) { 0575 // create a copy of the splits list in the transaction 0576 // loop over all splits 0577 for (auto& split : transaction.splits()) { 0578 // if the split is assigned to one of the selected payees, we need to modify it 0579 if (split.isMatched()) { 0580 auto tm = split.matchedTransaction(); 0581 for (auto& sm : tm.splits()) { 0582 if (payeeInList(m_selectedPayeesList, sm.payeeId())) { 0583 sm.setPayeeId(payee_id); // first modify payee in current split 0584 // then modify the split in our local copy of the transaction list 0585 tm.modifySplit(sm); // this does not modify the list object 'splits'! 0586 } 0587 } 0588 split.addMatch(tm); 0589 transaction.modifySplit(split); // this does not modify the list object 'splits'! 0590 } 0591 if (payeeInList(m_selectedPayeesList, split.payeeId())) { 0592 split.setPayeeId(payee_id); // first modify payee in current split 0593 // then modify the split in our local copy of the transaction list 0594 transaction.modifySplit(split); // this does not modify the list object 'splits'! 0595 } 0596 } // for - Splits 0597 file->modifyTransaction(transaction); // modify the transaction in the MyMoney object 0598 } // for - Transactions 0599 0600 // now loop over all schedules and reassign payees 0601 for (auto& schedule : used_schedules) { 0602 // create copy of transaction in current schedule 0603 auto trans = schedule.transaction(); 0604 // create copy of lists of splits 0605 for (auto& split : trans.splits()) { 0606 if (payeeInList(m_selectedPayeesList, split.payeeId())) { 0607 split.setPayeeId(payee_id); 0608 trans.modifySplit(split); // does not modify the list object 'splits'! 0609 } 0610 } // for - Splits 0611 // store transaction in current schedule 0612 schedule.setTransaction(trans); 0613 file->modifySchedule(schedule); // modify the schedule in the MyMoney engine 0614 } // for - Schedules 0615 0616 // reassign the payees in the loans that reference the deleted payees 0617 foreach (const MyMoneyAccount &account, usedAccounts) { 0618 MyMoneyAccountLoan loanAccount(account); 0619 loanAccount.setPayee(payee_id); 0620 file->modifyAccount(loanAccount); 0621 } 0622 0623 } catch (const MyMoneyException &e) { 0624 KMessageBox::detailedSorry(q, i18n("Unable to reassign payee of transaction/split"), e.what()); 0625 } 0626 } else { // if !translist.isEmpty() 0627 if (type == KPayeeReassignDlg::TypeMerge) { 0628 KMessageBox::sorry(q, i18n("Nothing to merge."), i18n("Merge Payees")); 0629 return false; 0630 } 0631 } 0632 0633 bool ignorecase; 0634 QStringList payeeNames; 0635 auto matchType = newPayee.matchData(ignorecase, payeeNames); 0636 QStringList deletedMatchPattern; 0637 0638 // now loop over all selected payees and remove them 0639 for (QList<MyMoneyPayee>::iterator it = m_selectedPayeesList.begin(); 0640 it != m_selectedPayeesList.end(); ++it) { 0641 if (newPayee.id() != (*it).id()) { 0642 if (addToMatchList) { 0643 QStringList matchPattern; 0644 auto mType = (*it).matchData(ignorecase, matchPattern); 0645 switch (mType) { 0646 case eMyMoney::Payee::MatchType::NameExact: 0647 matchPattern << QStringLiteral("^%1$").arg((*it).name()); 0648 break; 0649 case eMyMoney::Payee::MatchType::Name: 0650 matchPattern << (*it).name(); 0651 break; 0652 default: 0653 break; 0654 } 0655 if (!matchPattern.isEmpty()) { 0656 deletedMatchPattern << matchPattern; 0657 } 0658 } 0659 file->removePayee(*it); 0660 } 0661 } 0662 0663 // if we initially have no matching turned on, we just ignore the case (default) 0664 if (matchType == eMyMoney::Payee::MatchType::Disabled) 0665 ignorecase = true; 0666 0667 // update the destination payee if this was requested by the user 0668 if (addToMatchList && deletedMatchPattern.count() > 0) { 0669 // add new names to the list 0670 // TODO: it would be cool to somehow shrink the list to make better use 0671 // of regular expressions at this point. For now, we leave this task 0672 // to the user himeself. 0673 QStringList::const_iterator it_n; 0674 for (it_n = deletedMatchPattern.constBegin(); it_n != deletedMatchPattern.constEnd(); ++it_n) { 0675 if (matchType == eMyMoney::Payee::MatchType::Key) { 0676 // make sure we really need it and it is not caught by an existing regexp 0677 QStringList::const_iterator it_k; 0678 for (it_k = payeeNames.constBegin(); it_k != payeeNames.constEnd(); ++it_k) { 0679 QRegExp exp(*it_k, ignorecase ? Qt::CaseInsensitive : Qt::CaseSensitive); 0680 if (exp.indexIn(*it_n) != -1) 0681 break; 0682 } 0683 if (it_k == payeeNames.constEnd()) { 0684 payeeNames << addPatternOrName(*it_n); 0685 } 0686 } else if (payeeNames.contains(*it_n) == 0) 0687 payeeNames << addPatternOrName(*it_n); 0688 } 0689 0690 // and update the payee in the engine context 0691 // make sure to turn on matching for this payee in the right mode 0692 newPayee.setMatchData(eMyMoney::Payee::MatchType::Key, ignorecase, payeeNames); 0693 file->modifyPayee(newPayee); 0694 } 0695 ft.commit(); 0696 0697 // If we just deleted the payees, they sure don't exist anymore 0698 m_selectedPayeesList.clear(); 0699 0700 } catch (const MyMoneyException &e) { 0701 KMessageBox::detailedSorry(q, i18n("Unable to remove payee(s)"), e.what()); 0702 } 0703 0704 return true; 0705 } 0706 0707 /** 0708 * Check if a list contains a payee with a given id 0709 * 0710 * @param list const reference to value list 0711 * @param id const reference to id 0712 * 0713 * @retval true object has been found 0714 * @retval false object is not in list 0715 */ 0716 bool payeeInList(const QList<MyMoneyPayee>& list, const QString& id) const 0717 { 0718 bool rc = false; 0719 QList<MyMoneyPayee>::const_iterator it_p = list.begin(); 0720 while (it_p != list.end()) { 0721 if ((*it_p).id() == id) { 0722 rc = true; 0723 break; 0724 } 0725 ++it_p; 0726 } 0727 return rc; 0728 } 0729 0730 /** Checks whether the currently selected payee is "dirty" 0731 * @return true, if payee is modified (is "dirty"); false otherwise 0732 */ 0733 bool isDirty() const 0734 { 0735 return ui->m_updateButton->isEnabled(); 0736 } 0737 0738 /** Sets the payee's "dirty" (modified) status 0739 * @param dirty if true (default), payee will be set to dirty 0740 */ 0741 void setDirty(bool dirty) 0742 { 0743 ui->m_updateButton->setEnabled(dirty); 0744 } 0745 0746 KPayeesView *q_ptr; 0747 Ui::KPayeesView *ui; 0748 MyMoneyPayee m_payee; 0749 QString m_newName; 0750 MyMoneyContact *m_contact; 0751 int m_payeeRow; 0752 QList<int> m_payeeRows; 0753 0754 /** 0755 * List of selected payees 0756 */ 0757 QList<MyMoneyPayee> m_selectedPayeesList; 0758 0759 /** 0760 * q member holds a list of all transactions 0761 */ 0762 QList<QPair<MyMoneyTransaction, MyMoneySplit> > m_transactionList; 0763 0764 /** 0765 * q member holds the load state of page 0766 */ 0767 bool m_needLoad; 0768 0769 /** 0770 * Search widget for the list 0771 */ 0772 KListWidgetSearchLine* m_searchWidget; 0773 0774 /** 0775 * Semaphore to suppress loading during selection 0776 */ 0777 bool m_inSelection; 0778 0779 /** 0780 * q signals whether a payee can be edited 0781 **/ 0782 bool m_allowEditing; 0783 0784 /** 0785 * q holds the filter type 0786 */ 0787 int m_payeeFilterType; 0788 0789 AccountNamesFilterProxyModel *m_filterProxyModel; 0790 }; 0791 0792 #endif