File indexing completed on 2024-05-12 05:07:43

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: 2017 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include "kscheduledview_p.h"
0013 
0014 // ----------------------------------------------------------------------------
0015 // QT Includes
0016 
0017 #include <QMenu>
0018 
0019 // ----------------------------------------------------------------------------
0020 // KDE Includes
0021 
0022 // ----------------------------------------------------------------------------
0023 // Project Includes
0024 
0025 #include "keditloanwizard.h"
0026 #include "kmymoneyutils.h"
0027 #include "kmymoneysettings.h"
0028 #include "mymoneyexception.h"
0029 #include "keditscheduledlg.h"
0030 
0031 KScheduledView::KScheduledView(QWidget *parent) :
0032     KMyMoneyViewBase(*new KScheduledViewPrivate(this), parent)
0033 {
0034     typedef void(KScheduledView::*KScheduledViewFunc)();
0035     const QHash<eMenu::Action, KScheduledViewFunc> actionConnections {
0036         {eMenu::Action::NewSchedule,        &KScheduledView::slotNewSchedule},
0037         {eMenu::Action::EditSchedule,       &KScheduledView::slotEditSchedule},
0038         {eMenu::Action::DeleteSchedule,     &KScheduledView::slotDeleteSchedule},
0039         {eMenu::Action::DuplicateSchedule,  &KScheduledView::slotDuplicateSchedule},
0040         {eMenu::Action::EnterSchedule,      &KScheduledView::slotEnterSchedule},
0041         {eMenu::Action::SkipSchedule,       &KScheduledView::slotSkipSchedule},
0042     };
0043 
0044     for (auto a = actionConnections.cbegin(); a != actionConnections.cend(); ++a)
0045         connect(pActions[a.key()], &QAction::triggered, this, a.value());
0046 
0047     Q_D(KScheduledView);
0048     d->m_balanceWarning.reset(new KBalanceWarning(this));
0049 
0050     d->m_sharedToolbarActions.insert(eMenu::Action::FileNew, pActions[eMenu::Action::NewSchedule]);
0051 }
0052 
0053 KScheduledView::~KScheduledView()
0054 {
0055 }
0056 
0057 void KScheduledView::slotSettingsChanged()
0058 {
0059     Q_D(KScheduledView);
0060     d->settingsChanged();
0061 }
0062 
0063 void KScheduledView::showEvent(QShowEvent* event)
0064 {
0065     Q_D(KScheduledView);
0066     if (d->m_needLoad) {
0067         d->init();
0068         connect(d->ui->m_scheduleTree, &QWidget::customContextMenuRequested, this, [&](const QPoint& pos) {
0069             Q_D(KScheduledView);
0070             Q_EMIT requestCustomContextMenu(eMenu::Menu::Schedule, d->ui->m_scheduleTree->viewport()->mapToGlobal(pos));
0071         });
0072         connect(d->ui->m_scheduleTree, &KMyMoneyTreeView::startEdit, this, &KScheduledView::slotEditSchedule);
0073 
0074         connect(d->ui->m_scheduleTree->header(), &QHeaderView::sortIndicatorChanged, this, [&](int logicalIndex, Qt::SortOrder order) {
0075             Q_D(KScheduledView);
0076             d->m_filterModel->sort(logicalIndex, order);
0077         });
0078         connect(MyMoneyFile::instance()->schedulesModel(), &SchedulesModel::dataChanged, this, [&](const QModelIndex& from, const QModelIndex& to) {
0079             Q_UNUSED(from)
0080             Q_UNUSED(to)
0081             Q_D(KScheduledView);
0082             d->m_filterModel->invalidate();
0083         });
0084     }
0085     QWidget::showEvent(event);
0086 }
0087 
0088 void KScheduledView::updateActions(const SelectedObjects& selections)
0089 {
0090     Q_D(KScheduledView);
0091 
0092     const QVector<eMenu::Action> actionsToBeDisabled {
0093         eMenu::Action::EditSchedule, eMenu::Action::DuplicateSchedule, eMenu::Action::DeleteSchedule,
0094         eMenu::Action::EnterSchedule, eMenu::Action::SkipSchedule,
0095     };
0096 
0097     for (const auto& a : actionsToBeDisabled)
0098         pActions[a]->setEnabled(false);
0099 
0100     MyMoneySchedule sch;
0101     if (!selections.selection(SelectedObjects::Schedule).isEmpty()) {
0102         sch = MyMoneyFile::instance()->schedulesModel()->itemById(selections.selection(SelectedObjects::Schedule).at(0));
0103         if (!sch.id().isEmpty()) {
0104             pActions[eMenu::Action::EditSchedule]->setEnabled(true);
0105             pActions[eMenu::Action::DuplicateSchedule]->setEnabled(true);
0106             pActions[eMenu::Action::DeleteSchedule]->setEnabled(!MyMoneyFile::instance()->isReferenced(sch));
0107             if (!sch.isFinished()) {
0108                 pActions[eMenu::Action::EnterSchedule]->setEnabled(true);
0109                 // a schedule with a single occurrence cannot be skipped
0110                 if (sch.occurrence() != eMyMoney::Schedule::Occurrence::Once) {
0111                     pActions[eMenu::Action::SkipSchedule]->setEnabled(true);
0112                 }
0113             }
0114         }
0115     }
0116     d->m_currentSchedule = sch;
0117 }
0118 
0119 eDialogs::ScheduleResultCode KScheduledView::enterSchedule(MyMoneySchedule& schedule, bool autoEnter, bool extendedKeys)
0120 {
0121     Q_D(KScheduledView);
0122     return d->enterSchedule(schedule, autoEnter, extendedKeys);
0123 }
0124 
0125 void KScheduledView::slotEnterOverdueSchedules(const MyMoneyAccount& acc)
0126 {
0127     Q_D(KScheduledView);
0128     const auto file = MyMoneyFile::instance();
0129     auto schedules = file->scheduleList(acc.id(), eMyMoney::Schedule::Type::Any, eMyMoney::Schedule::Occurrence::Any, eMyMoney::Schedule::PaymentType::Any, QDate(), QDate(), true);
0130     if (!schedules.isEmpty()) {
0131         if (KMessageBox::questionTwoActions(
0132                 this,
0133                 i18n("KMyMoney has detected some overdue scheduled transactions for this account. Do you want to enter those scheduled transactions now?"),
0134                 i18n("Scheduled transactions found"),
0135                 KMMYesNo::yes(),
0136                 KMMYesNo::no())
0137             == KMessageBox::PrimaryAction) {
0138             QMap<QString, bool> skipMap;
0139             bool processedOne;
0140             auto rc = eDialogs::ScheduleResultCode::Enter;
0141             do {
0142                 processedOne = false;
0143                 QList<MyMoneySchedule>::const_iterator it_sch;
0144                 for (it_sch = schedules.constBegin(); (rc != eDialogs::ScheduleResultCode::Cancel) && (it_sch != schedules.constEnd()); ++it_sch) {
0145                     MyMoneySchedule sch(*(it_sch));
0146 
0147                     // and enter it if it is not on the skip list
0148                     if (skipMap.find((*it_sch).id()) == skipMap.end()) {
0149                         rc = d->enterSchedule(sch, false, true);
0150                         if (rc == eDialogs::ScheduleResultCode::Ignore) {
0151                             skipMap[(*it_sch).id()] = true;
0152                         }
0153                     }
0154                 }
0155 
0156                 // reload list (maybe this schedule needs to be added again)
0157                 schedules = file->scheduleList(acc.id(), eMyMoney::Schedule::Type::Any, eMyMoney::Schedule::Occurrence::Any, eMyMoney::Schedule::PaymentType::Any, QDate(), QDate(), true);
0158             } while (processedOne);
0159         }
0160     }
0161 }
0162 
0163 void KScheduledView::slotListViewExpanded(const QModelIndex& idx)
0164 {
0165     Q_D(KScheduledView);
0166     // we only need to check the top level items
0167     if (!idx.parent().isValid()) {
0168         const auto groupType = static_cast<eMyMoney::Schedule::Type>(idx.data(eMyMoney::Model::ScheduleTypeRole).toInt());
0169         d->m_expandedGroups[groupType] = true;
0170     }
0171 }
0172 
0173 void KScheduledView::slotListViewCollapsed(const QModelIndex& idx)
0174 {
0175     Q_D(KScheduledView);
0176     // we only need to check the top level items
0177     if (!idx.parent().isValid()) {
0178         const auto groupType = static_cast<eMyMoney::Schedule::Type>(idx.data(eMyMoney::Model::ScheduleTypeRole).toInt());
0179         d->m_expandedGroups[groupType] = false;
0180     }
0181 }
0182 
0183 void KScheduledView::slotSetSelectedItem(const QItemSelection& selected, const QItemSelection& deselected )
0184 {
0185     Q_UNUSED(deselected)
0186     SelectedObjects selections;
0187 
0188     if (!selected.isEmpty()) {
0189         QModelIndexList idxList = selected.indexes();
0190         if (!idxList.isEmpty()) {
0191             const auto objId = selected.indexes().front().data(eMyMoney::Model::IdRole).toString();
0192             selections.addSelection(SelectedObjects::Schedule, objId);
0193         }
0194     }
0195     Q_EMIT requestSelectionChange(selections);
0196 }
0197 
0198 void KScheduledView::slotNewSchedule()
0199 {
0200     KEditScheduleDlg::newSchedule(MyMoneyTransaction(), eMyMoney::Schedule::Occurrence::Monthly);
0201 }
0202 
0203 void KScheduledView::slotEditSchedule()
0204 {
0205     Q_D(KScheduledView);
0206     auto scheduleId = d->m_currentSchedule.id();
0207     if (scheduleId.isEmpty()) {
0208         scheduleId = pActions[eMenu::Action::EditSchedule]->data().toString();
0209     }
0210     try {
0211         auto schedule = MyMoneyFile::instance()->schedule(scheduleId);
0212         KEditScheduleDlg::editSchedule(schedule);
0213     } catch (const MyMoneyException &e) {
0214         KMessageBox::detailedError(this, i18n("Unknown scheduled transaction '%1'", d->m_currentSchedule.name()), QString::fromLatin1(e.what()));
0215     }
0216 }
0217 
0218 void KScheduledView::slotDeleteSchedule()
0219 {
0220     Q_D(KScheduledView);
0221     if (!d->m_currentSchedule.id().isEmpty()) {
0222         MyMoneyFileTransaction ft;
0223         try {
0224             MyMoneySchedule sched = MyMoneyFile::instance()->schedule(d->m_currentSchedule.id());
0225             QString msg = i18n("<p>Are you sure you want to delete the scheduled transaction <b>%1</b>?</p>", d->m_currentSchedule.name());
0226             if (sched.type() == eMyMoney::Schedule::Type::LoanPayment) {
0227                 msg += QString(" ");
0228                 msg += i18n("In case of loan payments it is currently not possible to recreate the scheduled transaction.");
0229             }
0230             if (KMessageBox::questionTwoActions(parentWidget(), msg, i18nc("@title:window", "Delete schedule"), KMMYesNo::yes(), KMMYesNo::no())
0231                 == KMessageBox::SecondaryAction)
0232                 return;
0233 
0234             MyMoneyFile::instance()->removeSchedule(sched);
0235             ft.commit();
0236 
0237         } catch (const MyMoneyException &e) {
0238             KMessageBox::detailedError(this, i18n("Unable to remove scheduled transaction '%1'", d->m_currentSchedule.name()), QString::fromLatin1(e.what()));
0239         }
0240     }
0241 }
0242 
0243 void KScheduledView::slotDuplicateSchedule()
0244 {
0245     Q_D(KScheduledView);
0246     // since we may jump here via code, we have to make sure to react only
0247     // if the action is enabled
0248     if (pActions[eMenu::Action::DuplicateSchedule]->isEnabled()) {
0249         MyMoneySchedule sch = d->m_currentSchedule;
0250         sch.clearId();
0251         sch.setLastPayment(QDate());
0252         sch.setName(i18nc("Copy of scheduled transaction name", "Copy of %1", sch.name()));
0253         // make sure that we set a valid next due date if the original next due date is invalid
0254         if (!sch.nextDueDate().isValid())
0255             sch.setNextDueDate(QDate::currentDate());
0256 
0257         MyMoneyFileTransaction ft;
0258         try {
0259             MyMoneyFile::instance()->addSchedule(sch);
0260             ft.commit();
0261 
0262             // select the new schedule in the view
0263             const auto indexes = d->m_filterModel->match(d->m_filterModel->index(0, 0),
0264                                  eMyMoney::Model::IdRole,
0265                                  QVariant(sch.id()),
0266                                  1,
0267                                  Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive));
0268             if (!indexes.isEmpty()) {
0269                 d->ui->m_scheduleTree->setCurrentIndex(indexes.at(0));
0270             }
0271 
0272         } catch (const MyMoneyException &e) {
0273             KMessageBox::detailedError(this, i18n("Unable to duplicate scheduled transaction: '%1'", d->m_currentSchedule.name()), QString::fromLatin1(e.what()));
0274         }
0275     }
0276 }
0277 
0278 void KScheduledView::slotEnterSchedule()
0279 {
0280     Q_D(KScheduledView);
0281     auto scheduleId = d->m_currentSchedule.id();
0282     if (scheduleId.isEmpty()) {
0283         scheduleId = pActions[eMenu::Action::EnterSchedule]->data().toString();
0284     }
0285     if (!scheduleId.isEmpty()) {
0286         try {
0287             auto schedule = MyMoneyFile::instance()->schedule(scheduleId);
0288             d->enterSchedule(schedule);
0289         } catch (const MyMoneyException &e) {
0290             KMessageBox::detailedError(this, i18n("Unknown scheduled transaction '%1'", d->m_currentSchedule.name()), QString::fromLatin1(e.what()));
0291         }
0292     }
0293 }
0294 
0295 void KScheduledView::slotSkipSchedule()
0296 {
0297     Q_D(KScheduledView);
0298     auto scheduleId = d->m_currentSchedule.id();
0299     if (scheduleId.isEmpty()) {
0300         scheduleId = pActions[eMenu::Action::SkipSchedule]->data().toString();
0301     }
0302     if (!scheduleId.isEmpty()) {
0303         try {
0304             auto schedule = MyMoneyFile::instance()->schedule(scheduleId);
0305             d->skipSchedule(schedule);
0306         } catch (const MyMoneyException &e) {
0307             KMessageBox::detailedError(this, i18n("Unknown scheduled transaction '%1'", d->m_currentSchedule.name()), QString::fromLatin1(e.what()));
0308         }
0309     }
0310 }