diff --git a/kmymoney/mymoney/mymoneycontact.h b/kmymoney/mymoney/mymoneycontact.h index bd8a55849..b60c97540 100644 --- a/kmymoney/mymoney/mymoneycontact.h +++ b/kmymoney/mymoney/mymoneycontact.h @@ -1,76 +1,78 @@ /* * Copyright 2014-2015 Cristian Oneț * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef MYMONEYCONTACT_H #define MYMONEYCONTACT_H #include #include #include #include "kmm_mymoney_export.h" /** * POD containig contact data, these fields are retrived based on an email address. */ struct ContactData { QString email; QString phoneNumber; QString street; + QString city; + QString state; QString locality; QString country; QString region; QString postalCode; }; Q_DECLARE_METATYPE(ContactData); class KJob; /** * This class can be used to retrieve contact fields from the address book based on an email * address. It's hides the KDE PIM libraries dependency so it can be made optional. */ class KMM_MYMONEY_EXPORT MyMoneyContact : public QObject { Q_OBJECT public: explicit MyMoneyContact(QObject *parent); /** * Properties of the default identity (the current user). */ bool ownerExists() const; QString ownerEmail() const; QString ownerFullName() const; public Q_SLOTS: /** * Use this slot to start retrieving contact data for an email. */ void fetchContact(const QString &email); Q_SIGNALS: /** * This signal is emitted when the contact data was retrieved. */ void contactFetched(const ContactData &identity); private Q_SLOTS: void searchContactResult(KJob *job); }; #endif // MYMONEYCONTACT_H diff --git a/kmymoney/views/kpayeesview.cpp b/kmymoney/views/kpayeesview.cpp index f5d77ae0c..68272bed5 100644 --- a/kmymoney/views/kpayeesview.cpp +++ b/kmymoney/views/kpayeesview.cpp @@ -1,720 +1,738 @@ /*************************************************************************** kpayeesview.cpp --------------- begin : Thu Jan 24 2002 copyright : (C) 2000-2002 by Michael Edwardes Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio Andreas Nicolai (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kpayeesview_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include #include "ui_kpayeesview.h" #include "kmymoneyviewbase_p.h" #include "kpayeeidentifierview.h" #include "mymoneypayee.h" #include "mymoneyexception.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneymoney.h" #include "mymoneytransactionfilter.h" #include "kmymoneysettings.h" #include "models.h" #include "accountsmodel.h" #include "mymoneysecurity.h" #include "mymoneycontact.h" #include "mymoneyprice.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "icons/icons.h" #include "transaction.h" #include "widgetenums.h" #include "mymoneyenums.h" #include "modelenums.h" #include "menuenums.h" using namespace Icons; // *** KPayeesView Implementation *** KPayeesView::KPayeesView(QWidget *parent) : KMyMoneyViewBase(*new KPayeesViewPrivate(this), parent) { connect(pActions[eMenu::Action::NewPayee], &QAction::triggered, this, &KPayeesView::slotNewPayee); connect(pActions[eMenu::Action::RenamePayee], &QAction::triggered, this, &KPayeesView::slotRenamePayee); connect(pActions[eMenu::Action::DeletePayee], &QAction::triggered, this, &KPayeesView::slotDeletePayee); connect(pActions[eMenu::Action::MergePayee], &QAction::triggered, this, &KPayeesView::slotMergePayee); } KPayeesView::~KPayeesView() { } void KPayeesView::slotChooseDefaultAccount() { Q_D(KPayeesView); MyMoneyFile* file = MyMoneyFile::instance(); QMap account_count; KMyMoneyRegister::RegisterItem* item = d->ui->m_register->firstItem(); while (item) { //only walk through selectable items. eg. transactions and not group markers if (item->isSelectable()) { auto t = dynamic_cast(item); if (!t) return; MyMoneySplit s = t->transaction().splitByPayee(d->m_payee.id()); const MyMoneyAccount& acc = file->account(s.accountId()); if (s.action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization) && acc.accountType() != eMyMoney::Account::Type::AssetLoan && !file->isTransfer(t->transaction()) && t->transaction().splitCount() == 2) { MyMoneySplit s0 = t->transaction().splitByAccount(s.accountId(), false); if (account_count.contains(s0.accountId())) { account_count[s0.accountId()]++; } else { account_count[s0.accountId()] = 1; } } } item = item->nextItem(); } QMap::Iterator most_frequent, iter; most_frequent = account_count.begin(); for (iter = account_count.begin(); iter != account_count.end(); ++iter) { if (iter.value() > most_frequent.value()) { most_frequent = iter; } } if (most_frequent != account_count.end()) { d->ui->checkEnableDefaultCategory->setChecked(true); d->ui->comboDefaultCategory->setSelected(most_frequent.key()); d->setDirty(true); } } void KPayeesView::slotClosePayeeIdentifierSource() { Q_D(KPayeesView); if (!d->m_needLoad) d->ui->payeeIdentifiers->closeSource(); } void KPayeesView::slotSelectByVariant(const QVariantList& variant, eView::Intent intent) { switch (intent) { case eView::Intent::ShowPayee: if (variant.count() == 3) slotSelectPayeeAndTransaction(variant.at(0).toString(), variant.at(1).toString(), variant.at(2).toString()); break; default: break; } } void KPayeesView::slotStartRename(QListWidgetItem* item) { Q_D(KPayeesView); d->m_allowEditing = true; d->ui->m_payeesList->editItem(item); } // This variant is only called when a single payee is selected and renamed. void KPayeesView::slotRenameSinglePayee(QListWidgetItem* p) { Q_D(KPayeesView); //if there is no current item selected, exit if (d->m_allowEditing == false || !d->ui->m_payeesList->currentItem() || p != d->ui->m_payeesList->currentItem()) return; //qDebug() << "[KPayeesView::slotRenamePayee]"; // create a copy of the new name without appended whitespaces QString new_name = p->text(); if (d->m_payee.name() != new_name) { MyMoneyFileTransaction ft; try { // check if we already have a payee with the new name try { // this function call will throw an exception, if the payee // hasn't been found. MyMoneyFile::instance()->payeeByName(new_name); // the name already exists, ask the user whether he's sure to keep the name if (KMessageBox::questionYesNo(this, i18n("A payee with the name '%1' already exists. It is not advisable to have " "multiple payees with the same identification name. Are you sure you would like " "to rename the payee?", new_name)) != KMessageBox::Yes) { p->setText(d->m_payee.name()); return; } } catch (const MyMoneyException &) { // all ok, the name is unique } d->m_payee.setName(new_name); d->m_newName = new_name; MyMoneyFile::instance()->modifyPayee(d->m_payee); // the above call to modifyPayee will reload the view so // all references and pointers to the view have to be // re-established. // make sure, that the record is visible even if it moved // out of sight due to the rename operation d->ensurePayeeVisible(d->m_payee.id()); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify payee"), QString::fromLatin1(e.what())); } } else { p->setText(new_name); } } void KPayeesView::slotSelectPayee(QListWidgetItem* cur, QListWidgetItem* prev) { Q_D(KPayeesView); Q_UNUSED(cur); Q_UNUSED(prev); d->m_allowEditing = false; } void KPayeesView::slotSelectPayee() { Q_D(KPayeesView); // check if the content of a currently selected payee was modified // and ask to store the data if (d->isDirty()) { if (KMessageBox::questionYesNo(this, i18n("Do you want to save the changes for %1?", d->m_newName), i18n("Save changes")) == KMessageBox::Yes) { d->m_inSelection = true; slotUpdatePayee(); d->m_inSelection = false; } } // make sure we always clear the selected list when listing again d->m_selectedPayeesList.clear(); // loop over all payees and count the number of payees, also // obtain last selected payee d->selectedPayees(d->m_selectedPayeesList); updatePayeeActions(d->m_selectedPayeesList); emit selectObjects(d->m_selectedPayeesList); if (d->m_selectedPayeesList.isEmpty()) { d->ui->m_tabWidget->setEnabled(false); // disable tab widget d->ui->m_balanceLabel->hide(); d->ui->m_deleteButton->setEnabled(false); //disable delete, rename and merge buttons d->ui->m_renameButton->setEnabled(false); d->ui->m_mergeButton->setEnabled(false); d->clearItemData(); d->m_payee = MyMoneyPayee(); d->ui->m_syncAddressbook->setEnabled(false); return; // make sure we don't access an undefined payee } d->ui->m_deleteButton->setEnabled(true); //re-enable delete button d->ui->m_syncAddressbook->setEnabled(true); // if we have multiple payees selected, clear and disable the payee information if (d->m_selectedPayeesList.count() > 1) { d->ui->m_tabWidget->setEnabled(false); // disable tab widget d->ui->m_renameButton->setEnabled(false); // disable also the rename button d->ui->m_mergeButton->setEnabled(true); d->ui->m_balanceLabel->hide(); d->clearItemData(); } else { d->ui->m_mergeButton->setEnabled(false); d->ui->m_renameButton->setEnabled(true); } // otherwise we have just one selected, enable payee information widget d->ui->m_tabWidget->setEnabled(true); d->ui->m_balanceLabel->show(); // as of now we are updating only the last selected payee, and until // selection mode of the QListView has been changed to Extended, this // will also be the only selection and behave exactly as before - Andreas try { d->m_payee = d->m_selectedPayeesList[0]; d->m_newName = d->m_payee.name(); d->ui->addressEdit->setEnabled(true); d->ui->addressEdit->setText(d->m_payee.address()); + d->ui->payeecityEdit->setEnabled(true); + d->ui->payeecityEdit->setText(d->m_payee.city()); + d->ui->payeestateEdit->setEnabled(true); + d->ui->payeestateEdit->setText(d->m_payee.state()); d->ui->postcodeEdit->setEnabled(true); d->ui->postcodeEdit->setText(d->m_payee.postcode()); d->ui->telephoneEdit->setEnabled(true); d->ui->telephoneEdit->setText(d->m_payee.telephone()); d->ui->emailEdit->setEnabled(true); d->ui->emailEdit->setText(d->m_payee.email()); d->ui->notesEdit->setText(d->m_payee.notes()); QStringList keys; bool ignorecase = false; auto type = d->m_payee.matchData(ignorecase, keys); d->ui->matchTypeCombo->setCurrentIndex(d->ui->matchTypeCombo->findData(static_cast(type))); d->ui->matchKeyEditList->clear(); d->ui->matchKeyEditList->insertStringList(keys); d->ui->checkMatchIgnoreCase->setChecked(ignorecase); d->ui->checkEnableDefaultCategory->setChecked(d->m_payee.defaultAccountEnabled()); d->ui->comboDefaultCategory->setSelected(d->m_payee.defaultAccountId()); d->ui->payeeIdentifiers->setSource(d->m_payee); slotPayeeDataChanged(); d->showTransactions(); } catch (const MyMoneyException &e) { qDebug("exception during display of payee: %s", e.what()); d->ui->m_register->clear(); d->m_selectedPayeesList.clear(); d->m_payee = MyMoneyPayee(); } d->m_allowEditing = true; } void KPayeesView::slotKeyListChanged() { Q_D(KPayeesView); bool rc = false; bool ignorecase = false; QStringList keys; d->m_payee.matchData(ignorecase, keys); if (static_cast(d->ui->matchTypeCombo->currentData().toUInt()) == eMyMoney::Payee::MatchType::Key) { rc |= (keys != d->ui->matchKeyEditList->items()); } d->setDirty(rc); } void KPayeesView::slotPayeeDataChanged() { Q_D(KPayeesView); bool rc = false; if (d->ui->m_tabWidget->isEnabled()) { rc |= ((d->m_payee.email().isEmpty() != d->ui->emailEdit->text().isEmpty()) || (!d->ui->emailEdit->text().isEmpty() && d->m_payee.email() != d->ui->emailEdit->text())); rc |= ((d->m_payee.address().isEmpty() != d->ui->addressEdit->toPlainText().isEmpty()) || (!d->ui->addressEdit->toPlainText().isEmpty() && d->m_payee.address() != d->ui->addressEdit->toPlainText())); + rc |= ((d->m_payee.city().isEmpty() != d->ui->payeecityEdit->text().isEmpty()) + || (!d->ui->payeecityEdit->text().isEmpty() && d->m_payee.city() != d->ui->payeecityEdit->text())); + rc |= ((d->m_payee.state().isEmpty() != d->ui->payeestateEdit->text().isEmpty()) + || (!d->ui->payeestateEdit->text().isEmpty() && d->m_payee.state() != d->ui->payeestateEdit->text())); rc |= ((d->m_payee.postcode().isEmpty() != d->ui->postcodeEdit->text().isEmpty()) || (!d->ui->postcodeEdit->text().isEmpty() && d->m_payee.postcode() != d->ui->postcodeEdit->text())); rc |= ((d->m_payee.telephone().isEmpty() != d->ui->telephoneEdit->text().isEmpty()) || (!d->ui->telephoneEdit->text().isEmpty() && d->m_payee.telephone() != d->ui->telephoneEdit->text())); rc |= ((d->m_payee.name().isEmpty() != d->m_newName.isEmpty()) || (!d->m_newName.isEmpty() && d->m_payee.name() != d->m_newName)); rc |= ((d->m_payee.notes().isEmpty() != d->ui->notesEdit->toPlainText().isEmpty()) || (!d->ui->notesEdit->toPlainText().isEmpty() && d->m_payee.notes() != d->ui->notesEdit->toPlainText())); bool ignorecase = false; QStringList keys; auto type = d->m_payee.matchData(ignorecase, keys); rc |= (static_cast(type) != d->ui->matchTypeCombo->currentData().toUInt()); d->ui->checkMatchIgnoreCase->setEnabled(false); d->ui->matchKeyEditList->setEnabled(false); if (static_cast(d->ui->matchTypeCombo->currentData().toUInt()) != eMyMoney::Payee::MatchType::Disabled) { d->ui->checkMatchIgnoreCase->setEnabled(true); // if we turn matching on, we default to 'ignore case' // TODO maybe make the default a user option if (type == eMyMoney::Payee::MatchType::Disabled && static_cast(d->ui->matchTypeCombo->currentData().toUInt()) != eMyMoney::Payee::MatchType::Disabled) d->ui->checkMatchIgnoreCase->setChecked(true); rc |= (ignorecase != d->ui->checkMatchIgnoreCase->isChecked()); if (static_cast(d->ui->matchTypeCombo->currentData().toUInt()) == eMyMoney::Payee::MatchType::Key) { d->ui->matchKeyEditList->setEnabled(true); rc |= (keys != d->ui->matchKeyEditList->items()); } } rc |= (d->ui->checkEnableDefaultCategory->isChecked() != d->m_payee.defaultAccountEnabled()); if (d->ui->checkEnableDefaultCategory->isChecked()) { d->ui->comboDefaultCategory->setEnabled(true); d->ui->labelDefaultCategory->setEnabled(true); // this is only going to understand the first in the list of selected accounts if (d->ui->comboDefaultCategory->getSelected().isEmpty()) { rc |= !d->m_payee.defaultAccountId().isEmpty(); } else { QString temp = d->ui->comboDefaultCategory->getSelected(); rc |= (temp.isEmpty() != d->m_payee.defaultAccountId().isEmpty()) || (!d->m_payee.defaultAccountId().isEmpty() && temp != d->m_payee.defaultAccountId()); } } else { d->ui->comboDefaultCategory->setEnabled(false); d->ui->labelDefaultCategory->setEnabled(false); } rc |= (d->m_payee.payeeIdentifiers() != d->ui->payeeIdentifiers->identifiers()); } d->setDirty(rc); } void KPayeesView::slotUpdatePayee() { Q_D(KPayeesView); if (d->isDirty()) { MyMoneyFileTransaction ft; d->setDirty(false); try { d->m_payee.setName(d->m_newName); d->m_payee.setAddress(d->ui->addressEdit->toPlainText()); + d->m_payee.setCity(d->ui->payeecityEdit->text()); + d->m_payee.setState(d->ui->payeestateEdit->text()); d->m_payee.setPostcode(d->ui->postcodeEdit->text()); d->m_payee.setTelephone(d->ui->telephoneEdit->text()); d->m_payee.setEmail(d->ui->emailEdit->text()); d->m_payee.setNotes(d->ui->notesEdit->toPlainText()); d->m_payee.setMatchData(static_cast(d->ui->matchTypeCombo->currentData().toUInt()), d->ui->checkMatchIgnoreCase->isChecked(), d->ui->matchKeyEditList->items()); d->m_payee.setDefaultAccountId(); d->m_payee.resetPayeeIdentifiers(d->ui->payeeIdentifiers->identifiers()); if (d->ui->checkEnableDefaultCategory->isChecked()) { QString temp; if (!d->ui->comboDefaultCategory->getSelected().isEmpty()) { temp = d->ui->comboDefaultCategory->getSelected(); d->m_payee.setDefaultAccountId(temp); } } MyMoneyFile::instance()->modifyPayee(d->m_payee); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify payee"), QString::fromLatin1(e.what())); } } } void KPayeesView::slotSyncAddressBook() { Q_D(KPayeesView); if (d->m_payeeRows.isEmpty()) { // empty list means no syncing is pending... foreach (auto item, d->ui->m_payeesList->selectedItems()) { d->m_payeeRows.append(d->ui->m_payeesList->row(item)); // ...so initialize one } d->ui->m_payeesList->clearSelection(); // otherwise slotSelectPayee will be run after every payee update // d->ui->m_syncAddressbook->setEnabled(false); // disallow concurrent syncs } if (d->m_payeeRows.count() <= d->m_payeeRow) { if (auto item = dynamic_cast(d->ui->m_payeesList->currentItem())) { // update ui if something is selected d->m_payee = item->payee(); d->ui->addressEdit->setText(d->m_payee.address()); + d->ui->payeecityEdit->setText(d->m_payee.city()); + d->ui->payeestateEdit->setText(d->m_payee.state()); d->ui->postcodeEdit->setText(d->m_payee.postcode()); d->ui->telephoneEdit->setText(d->m_payee.telephone()); } d->m_payeeRows.clear(); // that means end of sync d->m_payeeRow = 0; return; } if (auto item = dynamic_cast(d->ui->m_payeesList->item(d->m_payeeRows.at(d->m_payeeRow)))) d->m_payee = item->payee(); ++d->m_payeeRow; d->m_contact->fetchContact(d->m_payee.email()); // search for payee's data in addressbook and receive it in slotContactFetched } void KPayeesView::slotContactFetched(const ContactData &identity) { Q_D(KPayeesView); if (!identity.email.isEmpty()) { // empty e-mail means no identity fetched QString txt; if (!identity.street.isEmpty()) txt.append(identity.street + '\n'); if (!identity.locality.isEmpty()) { txt.append(identity.locality); - if (!identity.postalCode.isEmpty()) - txt.append(' ' + identity.postalCode + '\n'); + if (!identity.postalCode.isEmpty()) + txt.append(' ' + identity.postalCode + '\n'); else txt.append('\n'); } if (!identity.country.isEmpty()) txt.append(identity.country + '\n'); if (!txt.isEmpty() && d->m_payee.address().compare(txt) != 0) d->m_payee.setAddress(txt); + if (!identity.city.isEmpty() && d->m_payee.city().compare(identity.city) != 0) + d->m_payee.setCity(identity.city); + + if (!identity.state.isEmpty() && d->m_payee.state().compare(identity.state) != 0) + d->m_payee.setState(identity.state); + if (!identity.postalCode.isEmpty() && d->m_payee.postcode().compare(identity.postalCode) != 0) d->m_payee.setPostcode(identity.postalCode); if (!identity.phoneNumber.isEmpty() && d->m_payee.telephone().compare(identity.phoneNumber) != 0) d->m_payee.setTelephone(identity.phoneNumber); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyPayee(d->m_payee); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify payee"), QString::fromLatin1(e.what())); } } slotSyncAddressBook(); // process next payee } void KPayeesView::slotSendMail() { Q_D(KPayeesView); QRegularExpression re(".+@.+"); if (re.match(d->m_payee.email()).hasMatch()) QDesktopServices::openUrl(QUrl(QStringLiteral("mailto:?to=") + d->m_payee.email(), QUrl::TolerantMode)); } void KPayeesView::executeCustomAction(eView::Action action) { switch(action) { case eView::Action::Refresh: refresh(); break; case eView::Action::SetDefaultFocus: { Q_D(KPayeesView); QTimer::singleShot(0, d->m_searchWidget, SLOT(setFocus())); } break; case eView::Action::ClosePayeeIdentifierSource: slotClosePayeeIdentifierSource(); break; default: break; } } void KPayeesView::refresh() { Q_D(KPayeesView); if (isVisible()) { if (d->m_inSelection) { QTimer::singleShot(0, this, SLOT(refresh())); } else { d->loadPayees(); d->m_needsRefresh = false; } } else { d->m_needsRefresh = true; } } void KPayeesView::showEvent(QShowEvent* event) { if (MyMoneyFile::instance()->storageAttached()) { Q_D(KPayeesView); if (d->m_needLoad) d->init(); emit customActionRequested(View::Payees, eView::Action::AboutToShow); if (d->m_needsRefresh) refresh(); QList list; d->selectedPayees(list); emit selectObjects(list); } // don't forget base class implementation QWidget::showEvent(event); } void KPayeesView::updatePayeeActions(const QList &payees) { pActions[eMenu::Action::NewPayee]->setEnabled(true); const auto payeesCount = payees.count(); auto b = payeesCount == 1 ? true : false; pActions[eMenu::Action::RenamePayee]->setEnabled(b); b = payeesCount > 1 ? true : false; pActions[eMenu::Action::MergePayee]->setEnabled(b); b = payeesCount == 0 ? false : true; pActions[eMenu::Action::DeletePayee]->setEnabled(b); } void KPayeesView::slotSelectTransaction() { Q_D(KPayeesView); auto list = d->ui->m_register->selectedItems(); if (!list.isEmpty()) { const auto t = dynamic_cast(list[0]); if (t) emit selectByVariant(QVariantList {QVariant(t->split().accountId()), QVariant(t->transaction().id()) }, eView::Intent::ShowTransaction); } } void KPayeesView::slotSelectPayeeAndTransaction(const QString& payeeId, const QString& accountId, const QString& transactionId) { Q_D(KPayeesView); if (!isVisible()) return; try { // clear filter d->m_searchWidget->clear(); d->m_searchWidget->updateSearch(); // deselect all other selected items QList selectedItems = d->ui->m_payeesList->selectedItems(); QList::const_iterator payeesIt = selectedItems.constBegin(); while (payeesIt != selectedItems.constEnd()) { if (auto item = dynamic_cast(*payeesIt)) item->setSelected(false); ++payeesIt; } // find the payee in the list QListWidgetItem* it; for (int i = 0; i < d->ui->m_payeesList->count(); ++i) { it = d->ui->m_payeesList->item(i); auto item = dynamic_cast(it); if (item && item->payee().id() == payeeId) { d->ui->m_payeesList->scrollToItem(it, QAbstractItemView::PositionAtCenter); d->ui->m_payeesList->setCurrentItem(it); // active item and deselect all others d->ui->m_payeesList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it //make sure the payee selection is updated and transactions are updated accordingly slotSelectPayee(); KMyMoneyRegister::RegisterItem *registerItem = 0; for (i = 0; i < d->ui->m_register->rowCount(); ++i) { registerItem = d->ui->m_register->itemAtRow(i); if (auto t = dynamic_cast(registerItem)) { if (t->transaction().id() == transactionId && t->transaction().accountReferenced(accountId)) { d->ui->m_register->selectItem(registerItem); d->ui->m_register->ensureItemVisible(registerItem); break; } } } // quit out of outer for() loop break; } } } catch (const MyMoneyException &e) { qWarning("Unexpected exception in KPayeesView::slotSelectPayeeAndTransaction %s", e.what()); } } void KPayeesView::slotShowPayeesMenu(const QPoint& /*p*/) { Q_D(KPayeesView); if (dynamic_cast(d->ui->m_payeesList->currentItem())) { slotSelectPayee(); pMenus[eMenu::Menu::Payee]->exec(QCursor::pos()); } } void KPayeesView::slotHelp() { KHelpClient::invokeHelp("details.payees"); } void KPayeesView::slotChangeFilter(int index) { Q_D(KPayeesView); //update the filter type then reload the payees list d->m_payeeFilterType = index; d->loadPayees(); } void KPayeesView::slotNewPayee() { QString id; KMyMoneyUtils::newPayee(i18n("New Payee"), id); slotSelectPayeeAndTransaction(id); } void KPayeesView::slotRenamePayee() { Q_D(KPayeesView); if (d->ui->m_payeesList->currentItem() && d->ui->m_payeesList->selectedItems().count() == 1) { slotStartRename(d->ui->m_payeesList->currentItem()); } } void KPayeesView::slotDeletePayee() { Q_D(KPayeesView); if (d->m_selectedPayeesList.isEmpty()) return; // shouldn't happen // get confirmation from user QString prompt; if (d->m_selectedPayeesList.size() == 1) prompt = i18n("

Do you really want to remove the payee %1?

", d->m_selectedPayeesList.front().name()); else prompt = i18n("Do you really want to remove all selected payees?"); if (KMessageBox::questionYesNo(this, prompt, i18n("Remove Payee")) == KMessageBox::No) return; d->payeeReassign(KPayeeReassignDlg::TypeDelete); } void KPayeesView::slotMergePayee() { Q_D(KPayeesView); if (d->m_selectedPayeesList.size() < 1) return; // shouldn't happen if (KMessageBox::questionYesNo(this, i18n("

Do you really want to merge the selected payees?"), i18n("Merge Payees")) == KMessageBox::No) return; if (d->payeeReassign(KPayeeReassignDlg::TypeMerge)) // clean selection since we just deleted the selected payees d->m_selectedPayeesList.clear(); } diff --git a/kmymoney/views/kpayeesview.ui b/kmymoney/views/kpayeesview.ui index 16dcd0d20..51e348720 100644 --- a/kmymoney/views/kpayeesview.ui +++ b/kmymoney/views/kpayeesview.ui @@ -1,598 +1,650 @@ KPayeesView 0 0 885 567 4 0 Qt::Horizontal false 1 1 Your payees Creates a new payee Use this to create a new payee. New Rename the current selected payee Use this to start renaming the selected payee. Ren Delete selected payee(s) Use this to delete the selected payee. You can also select multiple payees to be deleted. Del Merge multiple selected payees Use this to merge multiple selected payees. Merge 1 1 true QAbstractItemView::ExtendedSelection 2 1 0 Transactions Balance: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false Address 4 4 - + 0 0 90 0 E-Mail: false + + + + 0 + 0 + + + + + 90 + 0 + + + + City: + + + false + + + + + + + + 0 + 0 + + + + + 90 + 0 + + + + State: + + + false + + + + 0 0 90 0 Postal Code: false - + 0 0 90 0 Telephone/Fax: false - + 0 0 90 0 Notes Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop false - + 0 0 90 0 Address: Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop false - + Creates new e-mail to your payee. Use this to create new e-mail to your payee. Send - + + + + + + + 0 1 - + 0 1 Qt::Vertical QSizePolicy::Expanding 21 20 Matching Match method Ignore Case List of matching names 0 1 This list contains the names that will match this payee if a transaction is imported from an external source. Keep in mind, that you can specify regular expressions here. List of matching names Default Category Use the default category for new transactions with this payee Default category: false Suggest a category Qt::Vertical QSizePolicy::Expanding 20 31 Account Numbers 0 0 Help Qt::Horizontal QSizePolicy::Expanding 260 21 Fetches the payee's data from your addressbook. Use this to fetch payee's data. Sync 0 0 Accepts the entered data and stores it Use this to accept the modified data. Update KEditListWidget QWidget

keditlistwidget.h
KComboBox QComboBox
kcombobox.h
KLineEdit QLineEdit
klineedit.h
KTextEdit QTextEdit
ktextedit.h
KMyMoneyRegister::Register QWidget
register.h
KMyMoneyAccountCombo KComboBox
kmymoneyaccountcombo.h
1
KPayeeIdentifierView QWidget
kpayeeidentifierview.h
1
m_tabWidget m_payeesList m_newButton m_renameButton m_deleteButton m_mergeButton m_filterBox addressEdit + payeecityEdit + payeestateEdit postcodeEdit telephoneEdit emailEdit m_sendMail notesEdit matchTypeCombo checkMatchIgnoreCase checkEnableDefaultCategory comboDefaultCategory buttonSuggestACategory m_helpButton m_syncAddressbook m_updateButton ../widgets/kmymoneyaccountcombo.h diff --git a/kmymoney/views/kpayeesview_p.h b/kmymoney/views/kpayeesview_p.h index 9191c084f..793e5ecad 100644 --- a/kmymoney/views/kpayeesview_p.h +++ b/kmymoney/views/kpayeesview_p.h @@ -1,773 +1,777 @@ /*************************************************************************** kpayeesview_p.h --------------- begin : Thu Jan 24 2002 copyright : (C) 2000-2002 by Michael Edwardes Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio Andreas Nicolai (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KPAYEESVIEW_P_H #define KPAYEESVIEW_P_H #include "kpayeesview.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include #include "ui_kpayeesview.h" #include "kmymoneyviewbase_p.h" #include "kmymoneyutils.h" #include "kmymoneymvccombo.h" #include "mymoneypayee.h" #include "mymoneyexception.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneymoney.h" #include "mymoneytransactionfilter.h" #include "kmymoneysettings.h" #include "kpayeereassigndlg.h" #include "models.h" #include "accountsmodel.h" #include "mymoneysecurity.h" #include "mymoneyschedule.h" #include "mymoneycontact.h" #include "mymoneyaccountloan.h" #include "mymoneysplit.h" #include "mymoneyprice.h" #include "mymoneytransaction.h" #include "icons/icons.h" #include "transaction.h" #include "widgetenums.h" #include "mymoneyenums.h" #include "modelenums.h" #include "menuenums.h" using namespace Icons; // *** KPayeeListItem Implementation *** class KPayeeListItem : public QListWidgetItem { public: /** * Constructor to be used to construct a payee entry object. * * @param parent pointer to the QListWidget object this entry should be * added to. * @param payee const reference to MyMoneyPayee for which * the QListWidget entry is constructed */ explicit KPayeeListItem(QListWidget *parent, const MyMoneyPayee& payee) : QListWidgetItem(parent, QListWidgetItem::UserType), m_payee(payee) { setText(payee.name()); // allow in column rename setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); } ~KPayeeListItem() { } const MyMoneyPayee& payee() const { return m_payee; } private: MyMoneyPayee m_payee; }; enum filterTypeE { eAllPayees = 0, eReferencedPayees = 1, eUnusedPayees = 2 }; class KPayeesViewPrivate : public KMyMoneyViewBasePrivate { Q_DECLARE_PUBLIC(KPayeesView) public: explicit KPayeesViewPrivate(KPayeesView *qq) : KMyMoneyViewBasePrivate(), q_ptr(qq), ui(new Ui::KPayeesView), m_contact(nullptr), m_payeeRow(0), m_needLoad(true), m_searchWidget(nullptr), m_inSelection(false), m_allowEditing(true), m_payeeFilterType(0), m_filterProxyModel(nullptr) { } ~KPayeesViewPrivate() { if(!m_needLoad) { // remember the splitter settings for startup auto grp = KSharedConfig::openConfig()->group("Last Use Settings"); grp.writeEntry("KPayeesViewSplitterSize", ui->m_splitter->saveState()); grp.sync(); } delete ui; } void init() { Q_Q(KPayeesView); m_needLoad = false; ui->setupUi(q); m_contact = new MyMoneyContact(q); m_filterProxyModel = new AccountNamesFilterProxyModel(q); m_filterProxyModel->setHideEquityAccounts(!KMyMoneySettings::expertMode()); m_filterProxyModel->addAccountGroup(QVector {eMyMoney::Account::Type::Asset, eMyMoney::Account::Type::Liability, eMyMoney::Account::Type::Income, eMyMoney::Account::Type::Expense, eMyMoney::Account::Type::Equity}); auto const model = Models::instance()->accountsModel(); m_filterProxyModel->setSourceModel(model); m_filterProxyModel->setSourceColumns(model->getColumns()); m_filterProxyModel->sort((int)eAccountsModel::Column::Account); ui->comboDefaultCategory->setModel(m_filterProxyModel); ui->matchTypeCombo->addItem(i18nc("@item No matching", "No matching"), static_cast(eMyMoney::Payee::MatchType::Disabled)); ui->matchTypeCombo->addItem(i18nc("@item Match Payees name partially", "Match Payees name (partial)"), static_cast(eMyMoney::Payee::MatchType::Name)); ui->matchTypeCombo->addItem(i18nc("@item Match Payees name exactly", "Match Payees name (exact)"), static_cast(eMyMoney::Payee::MatchType::NameExact)); ui->matchTypeCombo->addItem(i18nc("@item Search match in list", "Match on a name listed below"), static_cast(eMyMoney::Payee::MatchType::Key)); // create the searchline widget // and insert it into the existing layout m_searchWidget = new KListWidgetSearchLine(q, ui->m_payeesList); m_searchWidget->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); ui->m_payeesList->setContextMenuPolicy(Qt::CustomContextMenu); ui->m_listTopHLayout->insertWidget(0, m_searchWidget); //load the filter type ui->m_filterBox->addItem(i18nc("@item Show all payees", "All")); ui->m_filterBox->addItem(i18nc("@item Show only used payees", "Used")); ui->m_filterBox->addItem(i18nc("@item Show only unused payees", "Unused")); ui->m_filterBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); ui->m_newButton->setIcon(Icons::get(Icon::ListAddUser)); ui->m_renameButton->setIcon(Icons::get(Icon::UserProperties)); ui->m_deleteButton->setIcon(Icons::get(Icon::ListRemoveUser)); ui->m_mergeButton->setIcon(Icons::get(Icon::Merge)); ui->m_updateButton->setIcon(Icons::get(Icon::DialogOK)); ui->m_syncAddressbook->setIcon(Icons::get(Icon::Refresh)); ui->m_sendMail->setIcon(Icons::get(Icon::MailMessage)); ui->m_updateButton->setEnabled(false); ui->m_syncAddressbook->setEnabled(false); #ifndef KMM_ADDRESSBOOK_FOUND ui->m_syncAddressbook->hide(); #endif ui->matchTypeCombo->setCurrentIndex(0); ui->checkMatchIgnoreCase->setEnabled(false); ui->checkEnableDefaultCategory->setChecked(false); ui->labelDefaultCategory->setEnabled(false); ui->comboDefaultCategory->setEnabled(false); QList cols { eWidgets::eTransaction::Column::Date, eWidgets::eTransaction::Column::Account, eWidgets::eTransaction::Column::Detail, eWidgets::eTransaction::Column::ReconcileFlag, eWidgets::eTransaction::Column::Payment, eWidgets::eTransaction::Column::Deposit}; ui->m_register->setupRegister(MyMoneyAccount(), cols); ui->m_register->setSelectionMode(QTableWidget::SingleSelection); ui->m_register->setDetailsColumnType(eWidgets::eRegister::DetailColumn::AccountFirst); ui->m_balanceLabel->hide(); q->connect(m_contact, &MyMoneyContact::contactFetched, q, &KPayeesView::slotContactFetched); q->connect(ui->m_payeesList, static_cast(&QListWidget::currentItemChanged), q, static_cast(&KPayeesView::slotSelectPayee)); q->connect(ui->m_payeesList, &QListWidget::itemSelectionChanged, q, static_cast(&KPayeesView::slotSelectPayee)); q->connect(ui->m_payeesList, &QListWidget::itemDoubleClicked, q, &KPayeesView::slotStartRename); q->connect(ui->m_payeesList, &QListWidget::itemChanged, q, &KPayeesView::slotRenameSinglePayee); q->connect(ui->m_payeesList, &QWidget::customContextMenuRequested, q, &KPayeesView::slotShowPayeesMenu); q->connect(ui->m_newButton, &QAbstractButton::clicked, q, &KPayeesView::slotNewPayee); q->connect(ui->m_renameButton, &QAbstractButton::clicked, q, &KPayeesView::slotRenamePayee); q->connect(ui->m_deleteButton, &QAbstractButton::clicked, q, &KPayeesView::slotDeletePayee); q->connect(ui->m_mergeButton, &QAbstractButton::clicked, q, &KPayeesView::slotMergePayee); q->connect(ui->addressEdit, &QTextEdit::textChanged, q, &KPayeesView::slotPayeeDataChanged); + q->connect(ui->payeecityEdit, &QLineEdit::textChanged, q, &KPayeesView::slotPayeeDataChanged); + q->connect(ui->payeestateEdit, &QLineEdit::textChanged, q, &KPayeesView::slotPayeeDataChanged); q->connect(ui->postcodeEdit, &QLineEdit::textChanged, q, &KPayeesView::slotPayeeDataChanged); q->connect(ui->telephoneEdit, &QLineEdit::textChanged, q, &KPayeesView::slotPayeeDataChanged); q->connect(ui->emailEdit, &QLineEdit::textChanged, q, &KPayeesView::slotPayeeDataChanged); q->connect(ui->notesEdit, &QTextEdit::textChanged, q, &KPayeesView::slotPayeeDataChanged); q->connect(ui->matchKeyEditList, &KEditListWidget::changed, q, &KPayeesView::slotKeyListChanged); q->connect(ui->matchTypeCombo, static_cast(&QComboBox::currentIndexChanged), q, &KPayeesView::slotPayeeDataChanged); q->connect(ui->checkMatchIgnoreCase, &QAbstractButton::toggled, q, &KPayeesView::slotPayeeDataChanged); q->connect(ui->checkEnableDefaultCategory, &QAbstractButton::toggled, q, &KPayeesView::slotPayeeDataChanged); q->connect(ui->comboDefaultCategory, &KMyMoneyAccountCombo::accountSelected, q, &KPayeesView::slotPayeeDataChanged); q->connect(ui->buttonSuggestACategory, &QAbstractButton::clicked, q, &KPayeesView::slotChooseDefaultAccount); q->connect(ui->m_updateButton, &QAbstractButton::clicked, q, &KPayeesView::slotUpdatePayee); q->connect(ui->m_syncAddressbook, &QAbstractButton::clicked, q, &KPayeesView::slotSyncAddressBook); q->connect(ui->m_helpButton, &QAbstractButton::clicked, q, &KPayeesView::slotHelp); q->connect(ui->m_sendMail, &QAbstractButton::clicked, q, &KPayeesView::slotSendMail); q->connect(ui->m_register, &KMyMoneyRegister::Register::editTransaction, q, &KPayeesView::slotSelectTransaction); q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KPayeesView::refresh); q->connect(ui->m_filterBox, static_cast(&QComboBox::currentIndexChanged), q, &KPayeesView::slotChangeFilter); q->connect(ui->payeeIdentifiers, &KPayeeIdentifierView::dataChanged, q, &KPayeesView::slotPayeeDataChanged); // use the size settings of the last run (if any) KConfigGroup grp = KSharedConfig::openConfig()->group("Last Use Settings"); ui->m_splitter->restoreState(grp.readEntry("KPayeesViewSplitterSize", QByteArray())); ui->m_splitter->setChildrenCollapsible(false); //At start we haven't any payee selected ui->m_tabWidget->setEnabled(false); // disable tab widget ui->m_deleteButton->setEnabled(false); //disable delete, rename and merge buttons ui->m_renameButton->setEnabled(false); ui->m_mergeButton->setEnabled(false); m_payee = MyMoneyPayee(); // make sure we don't access an undefined payee clearItemData(); } void loadPayees() { Q_Q(KPayeesView); if (m_inSelection) return; QMap isSelected; QString id; MyMoneyFile* file = MyMoneyFile::instance(); // remember which items are selected in the list QList selectedItems = ui->m_payeesList->selectedItems(); QList::const_iterator payeesIt = selectedItems.constBegin(); while (payeesIt != selectedItems.constEnd()) { KPayeeListItem* item = dynamic_cast(*payeesIt); if (item) isSelected[item->payee().id()] = true; ++payeesIt; } // keep current selected item KPayeeListItem *currentItem = static_cast(ui->m_payeesList->currentItem()); if (currentItem) id = currentItem->payee().id(); m_allowEditing = false; // clear the list m_searchWidget->clear(); m_searchWidget->updateSearch(); ui->m_payeesList->clear(); ui->m_register->clear(); currentItem = 0; QListlist = file->payeeList(); QList::ConstIterator it; for (it = list.constBegin(); it != list.constEnd(); ++it) { if (m_payeeFilterType == eAllPayees || (m_payeeFilterType == eReferencedPayees && file->isReferenced(*it)) || (m_payeeFilterType == eUnusedPayees && !file->isReferenced(*it))) { KPayeeListItem* item = new KPayeeListItem(ui->m_payeesList, *it); if (item->payee().id() == id) currentItem = item; if (isSelected[item->payee().id()]) item->setSelected(true); } } ui->m_payeesList->sortItems(); if (currentItem) { ui->m_payeesList->setCurrentItem(currentItem); ui->m_payeesList->scrollToItem(currentItem); } m_filterProxyModel->invalidate(); ui->comboDefaultCategory->expandAll(); q->slotSelectPayee(0, 0); m_allowEditing = true; } void selectedPayees(QList& payeesList) const { QList selectedItems = ui->m_payeesList->selectedItems(); QList::ConstIterator itemsIt = selectedItems.constBegin(); while (itemsIt != selectedItems.constEnd()) { KPayeeListItem* item = dynamic_cast(*itemsIt); if (item) payeesList << item->payee(); ++itemsIt; } } void ensurePayeeVisible(const QString& id) { for (int i = 0; i < ui->m_payeesList->count(); ++i) { KPayeeListItem* p = dynamic_cast(ui->m_payeesList->item(0)); if (p && p->payee().id() == id) { ui->m_payeesList->scrollToItem(p, QAbstractItemView::PositionAtCenter); ui->m_payeesList->setCurrentItem(p); // active item and deselect all others ui->m_payeesList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it break; } } } void clearItemData() { ui->addressEdit->setText(QString()); + ui->payeecityEdit->setText(QString()); + ui->payeestateEdit->setText(QString()); ui->postcodeEdit->setText(QString()); ui->telephoneEdit->setText(QString()); ui->emailEdit->setText(QString()); ui->notesEdit->setText(QString()); showTransactions(); } /** * This method loads the m_transactionList, clears * the m_TransactionPtrVector and rebuilds and sorts * it according to the current settings. Then it * loads the m_transactionView with the transaction data. */ void showTransactions() { MyMoneyMoney balance; const auto file = MyMoneyFile::instance(); MyMoneySecurity base = file->baseCurrency(); // setup sort order ui->m_register->setSortOrder(KMyMoneySettings::sortSearchView()); // clear the register ui->m_register->clear(); if (m_selectedPayeesList.isEmpty() || !ui->m_tabWidget->isEnabled()) { ui->m_balanceLabel->setText(i18n("Balance: %1", balance.formatMoney(file->baseCurrency().smallestAccountFraction()))); return; } // setup the list and the pointer vector MyMoneyTransactionFilter filter; for (QList::const_iterator it = m_selectedPayeesList.constBegin(); it != m_selectedPayeesList.constEnd(); ++it) filter.addPayee((*it).id()); filter.setDateFilter(KMyMoneySettings::startDate().date(), QDate()); // retrieve the list from the engine file->transactionList(m_transactionList, filter); // create the elements for the register QList >::const_iterator it; QMap uniqueMap; MyMoneyMoney deposit, payment; int splitCount = 0; bool balanceAccurate = true; for (it = m_transactionList.constBegin(); it != m_transactionList.constEnd(); ++it) { const MyMoneySplit& split = (*it).second; MyMoneyAccount acc = file->account(split.accountId()); ++splitCount; uniqueMap[(*it).first.id()]++; KMyMoneyRegister::Register::transactionFactory(ui->m_register, (*it).first, (*it).second, uniqueMap[(*it).first.id()]); // take care of foreign currencies MyMoneyMoney val = split.shares().abs(); if (acc.currencyId() != base.id()) { const MyMoneyPrice &price = file->price(acc.currencyId(), base.id()); // in case the price is valid, we use it. Otherwise, we keep // a flag that tells us that the balance is somewhat inaccurate if (price.isValid()) { val *= price.rate(base.id()); } else { balanceAccurate = false; } } if (split.shares().isNegative()) { payment += val; } else { deposit += val; } } balance = deposit - payment; // add the group markers ui->m_register->addGroupMarkers(); // sort the transactions according to the sort setting ui->m_register->sortItems(); // remove trailing and adjacent markers ui->m_register->removeUnwantedGroupMarkers(); ui->m_register->updateRegister(true); // we might end up here with updates disabled on the register so // make sure that we enable updates here ui->m_register->setUpdatesEnabled(true); ui->m_balanceLabel->setText(i18n("Balance: %1%2", balanceAccurate ? "" : "~", balance.formatMoney(file->baseCurrency().smallestAccountFraction()))); } /** * Implement common task when deleting or merging payees */ bool payeeReassign(int type) { Q_Q(KPayeesView); if (!(type >= 0 && type < KPayeeReassignDlg::TypeCount)) return false; const auto file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; try { // create a transaction filter that contains all payees selected for removal MyMoneyTransactionFilter f = MyMoneyTransactionFilter(); for (QList::const_iterator it = m_selectedPayeesList.constBegin(); it != m_selectedPayeesList.constEnd(); ++it) { f.addPayee((*it).id()); } // request a list of all transactions that still use the payees in question QList translist = file->transactionList(f); // qDebug() << "[KPayeesView::slotDeletePayee] " << translist.count() << " transaction still assigned to payees"; // now get a list of all schedules that make use of one of the payees QList used_schedules; foreach (const auto schedule, file->scheduleList()) { // loop over all splits in the transaction of the schedule foreach (const auto split, schedule.transaction().splits()) { // is the payee in the split to be deleted? if (payeeInList(m_selectedPayeesList, split.payeeId())) { used_schedules.push_back(schedule); // remember this schedule break; } } } // qDebug() << "[KPayeesView::slotDeletePayee] " << used_schedules.count() << " schedules use one of the selected payees"; // and a list of all loan accounts that references one of the payees QList allAccounts; QList usedAccounts; file->accountList(allAccounts); foreach (const MyMoneyAccount &account, allAccounts) { if (account.isLoan()) { MyMoneyAccountLoan loanAccount(account); foreach (const MyMoneyPayee &payee, m_selectedPayeesList) { if (loanAccount.hasReferenceTo(payee.id())) { usedAccounts.append(account); } } } } MyMoneyPayee newPayee; bool addToMatchList = false; // if at least one payee is still referenced, we need to reassign its transactions first if (!translist.isEmpty() || !used_schedules.isEmpty() || !usedAccounts.isEmpty()) { // first create list with all non-selected payees QList remainingPayees; if (type == KPayeeReassignDlg::TypeMerge) { remainingPayees = m_selectedPayeesList; } else { remainingPayees = file->payeeList(); QList::iterator it_p; for (it_p = remainingPayees.begin(); it_p != remainingPayees.end();) { if (m_selectedPayeesList.contains(*it_p)) { it_p = remainingPayees.erase(it_p); } else { ++it_p; } } } // show error message if no payees remain if (remainingPayees.isEmpty()) { KMessageBox::sorry(q, i18n("At least one transaction/scheduled transaction or loan account is still referenced by a payee. " "Currently you have all payees selected. However, at least one payee must remain so " "that the transaction/scheduled transaction or loan account can be reassigned.")); return false; } // show transaction reassignment dialog KPayeeReassignDlg * dlg = new KPayeeReassignDlg(static_cast(type), q); KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart()); QString payee_id = dlg->show(remainingPayees); addToMatchList = dlg->addToMatchList(); delete dlg; // and kill the dialog if (payee_id.isEmpty()) return false; // the user aborted the dialog, so let's abort as well // try to get selected payee. If not possible and we are merging payees, // then we create a new one try { newPayee = file->payee(payee_id); } catch (const MyMoneyException &) { if (type == KPayeeReassignDlg::TypeMerge) { // it's ok to use payee_id for both arguments since the first is const, // so it's garantee not to change its content if (!KMyMoneyUtils::newPayee(payee_id, payee_id)) return false; // the user aborted the dialog, so let's abort as well newPayee = file->payee(payee_id); } else { return false; } } // TODO : check if we have a report that explicitively uses one of our payees // and issue an appropriate warning try { // now loop over all transactions and reassign payee for (auto& transaction : translist) { // create a copy of the splits list in the transaction // loop over all splits for (auto& split : transaction.splits()) { // if the split is assigned to one of the selected payees, we need to modify it if (split.isMatched()) { auto tm = split.matchedTransaction(); for (auto& sm : tm.splits()) { if (payeeInList(m_selectedPayeesList, sm.payeeId())) { sm.setPayeeId(payee_id); // first modify payee in current split // then modify the split in our local copy of the transaction list tm.modifySplit(sm); // this does not modify the list object 'splits'! } } split.addMatch(tm); transaction.modifySplit(split); // this does not modify the list object 'splits'! } if (payeeInList(m_selectedPayeesList, split.payeeId())) { split.setPayeeId(payee_id); // first modify payee in current split // then modify the split in our local copy of the transaction list transaction.modifySplit(split); // this does not modify the list object 'splits'! } } // for - Splits file->modifyTransaction(transaction); // modify the transaction in the MyMoney object } // for - Transactions // now loop over all schedules and reassign payees for (auto& schedule : used_schedules) { // create copy of transaction in current schedule auto trans = schedule.transaction(); // create copy of lists of splits for (auto& split : trans.splits()) { if (payeeInList(m_selectedPayeesList, split.payeeId())) { split.setPayeeId(payee_id); trans.modifySplit(split); // does not modify the list object 'splits'! } } // for - Splits // store transaction in current schedule schedule.setTransaction(trans); file->modifySchedule(schedule); // modify the schedule in the MyMoney engine } // for - Schedules // reassign the payees in the loans that reference the deleted payees foreach (const MyMoneyAccount &account, usedAccounts) { MyMoneyAccountLoan loanAccount(account); loanAccount.setPayee(payee_id); file->modifyAccount(loanAccount); } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(q, i18n("Unable to reassign payee of transaction/split"), e.what()); } } else { // if !translist.isEmpty() if (type == KPayeeReassignDlg::TypeMerge) { KMessageBox::sorry(q, i18n("Nothing to merge."), i18n("Merge Payees")); return false; } } bool ignorecase; QStringList payeeNames; auto matchType = newPayee.matchData(ignorecase, payeeNames); QStringList deletedPayeeNames; // now loop over all selected payees and remove them for (QList::iterator it = m_selectedPayeesList.begin(); it != m_selectedPayeesList.end(); ++it) { if (newPayee.id() != (*it).id()) { if (addToMatchList) { deletedPayeeNames << (*it).name(); } file->removePayee(*it); } } // if we initially have no matching turned on, we just ignore the case (default) if (matchType == eMyMoney::Payee::MatchType::Disabled) ignorecase = true; // update the destination payee if this was requested by the user if (addToMatchList && deletedPayeeNames.count() > 0) { // add new names to the list // TODO: it would be cool to somehow shrink the list to make better use // of regular expressions at this point. For now, we leave this task // to the user himeself. QStringList::const_iterator it_n; for (it_n = deletedPayeeNames.constBegin(); it_n != deletedPayeeNames.constEnd(); ++it_n) { if (matchType == eMyMoney::Payee::MatchType::Key) { // make sure we really need it and it is not caught by an existing regexp QStringList::const_iterator it_k; for (it_k = payeeNames.constBegin(); it_k != payeeNames.constEnd(); ++it_k) { QRegExp exp(*it_k, ignorecase ? Qt::CaseInsensitive : Qt::CaseSensitive); if (exp.indexIn(*it_n) != -1) break; } if (it_k == payeeNames.constEnd()) payeeNames << QRegExp::escape(*it_n); } else if (payeeNames.contains(*it_n) == 0) payeeNames << QRegExp::escape(*it_n); } // and update the payee in the engine context // make sure to turn on matching for this payee in the right mode newPayee.setMatchData(eMyMoney::Payee::MatchType::Key, ignorecase, payeeNames); file->modifyPayee(newPayee); } ft.commit(); // If we just deleted the payees, they sure don't exist anymore m_selectedPayeesList.clear(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(q, i18n("Unable to remove payee(s)"), e.what()); } return true; } /** * Check if a list contains a payee with a given id * * @param list const reference to value list * @param id const reference to id * * @retval true object has been found * @retval false object is not in list */ bool payeeInList(const QList& list, const QString& id) const { bool rc = false; QList::const_iterator it_p = list.begin(); while (it_p != list.end()) { if ((*it_p).id() == id) { rc = true; break; } ++it_p; } return rc; } /** Checks whether the currently selected payee is "dirty" * @return true, if payee is modified (is "dirty"); false otherwise */ bool isDirty() const { return ui->m_updateButton->isEnabled(); } /** Sets the payee's "dirty" (modified) status * @param dirty if true (default), payee will be set to dirty */ void setDirty(bool dirty) { ui->m_updateButton->setEnabled(dirty); } KPayeesView *q_ptr; Ui::KPayeesView *ui; MyMoneyPayee m_payee; QString m_newName; MyMoneyContact *m_contact; int m_payeeRow; QList m_payeeRows; /** * List of selected payees */ QList m_selectedPayeesList; /** * q member holds a list of all transactions */ QList > m_transactionList; /** * q member holds the load state of page */ bool m_needLoad; /** * Search widget for the list */ KListWidgetSearchLine* m_searchWidget; /** * Semaphore to suppress loading during selection */ bool m_inSelection; /** * q signals whether a payee can be edited **/ bool m_allowEditing; /** * q holds the filter type */ int m_payeeFilterType; AccountNamesFilterProxyModel *m_filterProxyModel; }; #endif