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 #ifndef KSCHEDULEDVIEW_P_H
0013 #define KSCHEDULEDVIEW_P_H
0014 
0015 #include "kscheduledview.h"
0016 
0017 // ----------------------------------------------------------------------------
0018 // QT Includes
0019 
0020 #include <QList>
0021 #include <QAction>
0022 #include <QScopedPointer>
0023 #include <QDebug>
0024 
0025 // ----------------------------------------------------------------------------
0026 // KDE Includes
0027 
0028 #include <KLocalizedString>
0029 #include <KConfig>
0030 #include <KMessageBox>
0031 #include <KSharedConfig>
0032 
0033 // ----------------------------------------------------------------------------
0034 // Project Includes
0035 
0036 #include "ui_kscheduledview.h"
0037 #include "kmymoneyviewbase_p.h"
0038 #include "kmymoneysettings.h"
0039 #include "kenterscheduledlg.h"
0040 #include "kbalancewarning.h"
0041 #include "kconfirmmanualenterdlg.h"
0042 #include "kmymoneymvccombo.h"
0043 #include "kmymoneyutils.h"
0044 #include "kmymoneysettings.h"
0045 #include "mymoneyexception.h"
0046 #include "mymoneyutils.h"
0047 #include "mymoneyaccount.h"
0048 #include "mymoneymoney.h"
0049 #include "mymoneysecurity.h"
0050 #include "mymoneyschedule.h"
0051 #include "mymoneyfile.h"
0052 #include "mymoneypayee.h"
0053 #include "mymoneysplit.h"
0054 #include "mymoneytransaction.h"
0055 #include "mymoneyenums.h"
0056 #include "menuenums.h"
0057 #include "dialogenums.h"
0058 #include "schedulesmodel.h"
0059 #include "scheduleproxymodel.h"
0060 
0061 class KScheduledViewPrivate : public KMyMoneyViewBasePrivate
0062 {
0063     Q_DECLARE_PUBLIC(KScheduledView)
0064 
0065 public:
0066     explicit KScheduledViewPrivate(KScheduledView* qq)
0067         : KMyMoneyViewBasePrivate(qq)
0068         , ui(new Ui::KScheduledView)
0069         , m_filterModel(nullptr)
0070         , m_needLoad(true)
0071         , m_editingCanceled(false)
0072         , m_balanceWarning(nullptr)
0073     {
0074     }
0075 
0076     ~KScheduledViewPrivate()
0077     {
0078         if(!m_needLoad)
0079             writeConfig();
0080         delete ui;
0081     }
0082 
0083     void init()
0084     {
0085         Q_Q(KScheduledView);
0086         m_needLoad = false;
0087         ui->setupUi(q);
0088 
0089         //enable custom context menu
0090         ui->m_scheduleTree->setContextMenuPolicy(Qt::CustomContextMenu);
0091         ui->m_scheduleTree->setSelectionMode(QAbstractItemView::SingleSelection);
0092 
0093         m_filterModel = new ScheduleProxyModel(q);
0094         m_filterModel->setSourceModel(MyMoneyFile::instance()->schedulesModel());
0095         m_filterModel->setSortLocaleAware(true);
0096         m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
0097         m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
0098         m_filterModel->setFilterKeyColumn(-1);
0099 
0100         ui->m_scheduleTree->setModel(m_filterModel);
0101 
0102         readConfig();
0103 
0104         q->connect(ui->m_qbuttonNew, &QAbstractButton::clicked, pActions[eMenu::Action::NewSchedule], &QAction::trigger);
0105         q->connect(ui->m_searchWidget, &QLineEdit::textChanged, m_filterModel, &QSortFilterProxyModel::setFilterFixedString);
0106 
0107         KGuiItem::assign(ui->m_qbuttonNew, KMyMoneyUtils::scheduleNewGuiItem());
0108 
0109         q->connect(ui->m_scheduleTree->selectionModel(), &QItemSelectionModel::selectionChanged, q, &KScheduledView::slotSetSelectedItem);
0110 
0111         q->connect(ui->m_scheduleTree, &QTreeView::expanded, q, &KScheduledView::slotListViewExpanded);
0112         q->connect(ui->m_scheduleTree, &QTreeView::collapsed, q, &KScheduledView::slotListViewCollapsed);
0113 
0114         // update settings
0115         settingsChanged();
0116 
0117         // Setup collapsed and expanded groups
0118         const auto model = ui->m_scheduleTree->model();
0119         const auto rows = model->rowCount();
0120         for (int row = 0; row < rows; ++row) {
0121             const auto idx = model->index(row, 0);
0122             const auto groupType = static_cast<eMyMoney::Schedule::Type>(idx.data(eMyMoney::Model::ScheduleTypeRole).toInt());
0123             if (m_expandedGroups.contains(groupType)) {
0124                 ui->m_scheduleTree->setExpanded(idx, m_expandedGroups[groupType]);
0125             }
0126         }
0127 
0128         m_focusWidget = ui->m_searchWidget;
0129     }
0130 
0131     void settingsChanged()
0132     {
0133         if (m_filterModel) {
0134             m_filterModel->setHideFinishedSchedules(KMyMoneySettings::hideFinishedSchedules());
0135             m_filterModel->invalidate();
0136         }
0137     }
0138 
0139     void readConfig()
0140     {
0141         KSharedConfigPtr config = KSharedConfig::openConfig();
0142         KConfigGroup grp = config->group("Last Use Settings");
0143         m_expandedGroups[eMyMoney::Schedule::Type::Bill] = grp.readEntry("KScheduleView_openBills", true);
0144         m_expandedGroups[eMyMoney::Schedule::Type::Deposit] = grp.readEntry("KScheduleView_openDeposits", true);
0145         m_expandedGroups[eMyMoney::Schedule::Type::Transfer] = grp.readEntry("KScheduleView_openTransfers", true);
0146         m_expandedGroups[eMyMoney::Schedule::Type::LoanPayment] = grp.readEntry("KScheduleView_openLoans", true);
0147         QByteArray columns;
0148         columns = grp.readEntry("KScheduleView_treeState", columns);
0149         ui->m_scheduleTree->header()->restoreState(columns);
0150         ui->m_scheduleTree->header()->setFont(KMyMoneySettings::listHeaderFontEx());
0151         int sortCol = grp.readEntry<int>("KScheduleView_sortColumn", SchedulesModel::Column::Name);
0152         auto sortOrder = static_cast<Qt::SortOrder>(grp.readEntry<int>("KScheduleView_sortOrder", Qt::AscendingOrder));
0153         m_filterModel->sort(sortCol, sortOrder);
0154         ui->m_scheduleTree->setSortingEnabled(true);
0155         ui->m_scheduleTree->setAllColumnsShowFocus(true);
0156         ui->m_scheduleTree->setAlternatingRowColors(true);
0157         ui->m_scheduleTree->header()->setSortIndicatorShown(true);
0158     }
0159 
0160     void writeConfig()
0161     {
0162         KSharedConfigPtr config = KSharedConfig::openConfig();
0163         KConfigGroup grp = config->group("Last Use Settings");
0164         grp.writeEntry("KScheduleView_openBills", m_expandedGroups[eMyMoney::Schedule::Type::Bill]);
0165         grp.writeEntry("KScheduleView_openDeposits", m_expandedGroups[eMyMoney::Schedule::Type::Deposit]);
0166         grp.writeEntry("KScheduleView_openTransfers", m_expandedGroups[eMyMoney::Schedule::Type::Transfer]);
0167         grp.writeEntry("KScheduleView_openLoans", m_expandedGroups[eMyMoney::Schedule::Type::LoanPayment]);
0168         QByteArray columns = ui->m_scheduleTree->header()->saveState();
0169         grp.writeEntry("KScheduleView_treeState", columns);
0170         grp.writeEntry("KScheduleView_sortColumn", m_filterModel->sortColumn());
0171         grp.writeEntry("KScheduleView_sortOrder", static_cast<int>(m_filterModel->sortOrder()));
0172         config->sync();
0173     }
0174 
0175     /**
0176       * This method allows to enter the next scheduled transaction of
0177       * the given schedule @a s. In case @a extendedKeys is @a true,
0178       * the given schedule can also be skipped or ignored.
0179       * If @a autoEnter is @a true and the schedule does not contain
0180       * an estimated value, the schedule is entered as is without further
0181       * interaction with the user. In all other cases, the user will
0182       * be presented a dialog and allowed to adjust the values for this
0183       * instance of the schedule.
0184       *
0185       * The transaction will be created and entered into the ledger
0186       * and the schedule updated.
0187       */
0188     eDialogs::ScheduleResultCode enterSchedule(MyMoneySchedule& schedule, bool autoEnter = false, bool extendedKeys = false)
0189     {
0190         Q_Q(KScheduledView);
0191         auto rc = eDialogs::ScheduleResultCode::Cancel;
0192         if (!schedule.id().isEmpty()) {
0193             try {
0194                 schedule = MyMoneyFile::instance()->schedule(schedule.id());
0195             } catch (const MyMoneyException &e) {
0196                 KMessageBox::detailedError(q, i18n("Unable to enter scheduled transaction '%1'", schedule.name()), e.what());
0197                 return rc;
0198             }
0199 
0200             QWidget* parent = QApplication::activeWindow();
0201             QPointer<KEnterScheduleDlg> dlg = new KEnterScheduleDlg(parent, schedule);
0202 
0203             try {
0204                 QDate origDueDate = schedule.nextDueDate();
0205 
0206                 dlg->setShowExtendedKeys(extendedKeys);
0207                 KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart());
0208 
0209                 auto torig = dlg->transaction();
0210                 MyMoneyTransaction taccepted(torig);
0211                 // force actions to be available no matter what (will be updated according to the state during
0212                 // slotTransactionsEnter or cancelEditing)
0213                 pActions[eMenu::Action::CancelTransaction]->setEnabled(true);
0214                 pActions[eMenu::Action::EnterTransaction]->setEnabled(true);
0215 
0216                 KConfirmManualEnterDlg::Action action = KConfirmManualEnterDlg::ModifyOnce;
0217                 if (!autoEnter || !schedule.isFixed()) {
0218                     while (dlg != nullptr) {
0219                         rc = eDialogs::ScheduleResultCode::Cancel;
0220                         if ((dlg->exec() == QDialog::Accepted) && (dlg != nullptr)) {
0221                             rc = dlg->resultCode();
0222                             if (rc == eDialogs::ScheduleResultCode::Enter) {
0223                                 taccepted = dlg->transaction();
0224                                 // make sure to suppress comparison of some data: postDate
0225                                 torig.setPostDate(taccepted.postDate());
0226 
0227                                 // for loans we only have the option to enter, so we don't ask
0228                                 if (schedule.type() != eMyMoney::Schedule::Type::LoanPayment) {
0229                                     if (torig != taccepted) {
0230                                         QPointer<KConfirmManualEnterDlg> cdlg = new KConfirmManualEnterDlg(schedule, q);
0231                                         cdlg->loadTransactions(torig, taccepted);
0232                                         if ((cdlg->exec() == QDialog::Accepted) && (cdlg != nullptr)) {
0233                                             action = cdlg->action();
0234                                             break;
0235                                         }
0236                                         // the user has chosen 'cancel' during confirmation,
0237                                         // we go back to the editor
0238                                         continue;
0239                                     }
0240                                 }
0241                                 break;
0242 
0243                             } else if (rc == eDialogs::ScheduleResultCode::Skip) {
0244                                 skipSchedule(schedule);
0245                             }
0246                         } else {
0247                             if (autoEnter) {
0248                                 if (KMessageBox::warningTwoActions(
0249                                         q,
0250                                         i18n("Are you sure you wish to stop this scheduled transaction from being entered into the register?\n\nKMyMoney will "
0251                                              "prompt you again next time it starts unless you manually enter it later."),
0252                                         i18nc("@title:window", "Stop entering schedule"),
0253                                         KMMYesNo::yes(),
0254                                         KMMYesNo::no())
0255                                     == KMessageBox::SecondaryAction) {
0256                                     // the user has chosen 'No' for the above question,
0257                                     // we go back to the editor
0258                                     continue;
0259                                 }
0260                             }
0261                         }
0262                         cancelEditing();
0263                         break;
0264                     }
0265                 }
0266 
0267                 // if we still have the editor around here, the user did not cancel
0268                 if ((dlg != nullptr) && (!m_editingCanceled)) {
0269                     MyMoneyFileTransaction ft;
0270                     try {
0271                         MyMoneyTransaction t(taccepted);
0272                         // add the new transaction
0273                         switch (action) {
0274                         case KConfirmManualEnterDlg::UseOriginal:
0275                             // setup widgets with original transaction data
0276                             t = torig;
0277                             break;
0278 
0279                         case KConfirmManualEnterDlg::ModifyAlways:
0280                             torig = taccepted;
0281                             torig.setPostDate(origDueDate);
0282                             schedule.setTransaction(torig);
0283                             break;
0284 
0285                         case KConfirmManualEnterDlg::ModifyOnce:
0286                             break;
0287                         }
0288 
0289                         MyMoneyFile::instance()->addTransaction(t);
0290 
0291                         // we should not need this because addTransaction() does
0292                         // update the data, but we want to stay on the safe side
0293                         if (!t.id().isEmpty()) {
0294                             t = MyMoneyFile::instance()->transaction(t.id());
0295                             schedule.setLastPayment(t.postDate());
0296                         }
0297 
0298                         // in case the next due date is invalid, the schedule is finished
0299                         // we mark it as such by setting the next due date to one day past the end
0300                         QDate nextDueDate = schedule.nextPayment(origDueDate);
0301                         if (!nextDueDate.isValid()) {
0302                             schedule.setNextDueDate(schedule.endDate().addDays(1));
0303                         } else {
0304                             schedule.setNextDueDate(nextDueDate);
0305                         }
0306                         MyMoneyFile::instance()->modifySchedule(schedule);
0307                         rc = eDialogs::ScheduleResultCode::Enter;
0308 
0309                         ft.commit();
0310                     } catch (const MyMoneyException& e) {
0311                         KMessageBox::detailedError(q, i18n("Unable to enter scheduled transaction '%1'", schedule.name()), e.what());
0312                     }
0313                 }
0314             } catch (const MyMoneyException &e) {
0315                 KMessageBox::detailedError(q, i18n("Unable to enter scheduled transaction '%1'", schedule.name()), e.what());
0316             }
0317             delete dlg;
0318         }
0319         return rc;
0320     }
0321 
0322     void cancelEditing()
0323     {
0324         // since we jump here via code, we have to make sure to react only
0325         // if the action is enabled
0326         if (pActions[eMenu::Action::CancelTransaction]->isEnabled()) {
0327             // make sure, we block the enter function
0328             pActions[eMenu::Action::EnterTransaction]->setEnabled(false);
0329             m_editingCanceled = true;
0330         }
0331     }
0332 
0333     /**
0334       * This method allows to skip the next scheduled transaction of
0335       * the given schedule @a s.
0336       *
0337       */
0338     void skipSchedule(MyMoneySchedule& schedule)
0339     {
0340         Q_Q(KScheduledView);
0341         const auto parentWidget = QApplication::activeWindow();
0342 
0343         if (!schedule.id().isEmpty()) {
0344             try {
0345                 schedule = MyMoneyFile::instance()->schedule(schedule.id());
0346                 if (!schedule.isFinished()) {
0347                     if (schedule.occurrence() != eMyMoney::Schedule::Occurrence::Once) {
0348                         QDate next = schedule.nextDueDate();
0349                         if (!schedule.isFinished()
0350                             && (KMessageBox::questionTwoActions(parentWidget,
0351                                                                 i18n("<qt>Do you really want to skip the <b>%1</b> transaction scheduled for <b>%2</b>?</qt>",
0352                                                                      schedule.name(),
0353                                                                      MyMoneyUtils::formatDate(next)),
0354                                                                 i18nc("@title:window", "Skip scheduled transaction"),
0355                                                                 KMMYesNo::yes(),
0356                                                                 KMMYesNo::no()))
0357                                 == KMessageBox::PrimaryAction) {
0358                             MyMoneyFileTransaction ft;
0359                             schedule.setLastPayment(next);
0360                             schedule.setNextDueDate(schedule.nextPayment(next));
0361                             MyMoneyFile::instance()->modifySchedule(schedule);
0362                             ft.commit();
0363                         }
0364                     }
0365                 }
0366             } catch (const MyMoneyException &e) {
0367                 KMessageBox::detailedError(q, i18n("<qt>Unable to skip scheduled transaction <b>%1</b>.</qt>", schedule.name()), e.what());
0368             }
0369         }
0370     }
0371 
0372     Ui::KScheduledView* ui;
0373     ScheduleProxyModel* m_filterModel;
0374     QHash<eMyMoney::Schedule::Type, bool> m_expandedGroups;
0375 
0376     /**
0377       * This member holds the initial load state of the view
0378       */
0379     bool m_needLoad;
0380     bool m_editingCanceled;
0381     MyMoneySchedule m_currentSchedule;
0382 
0383     QScopedPointer<KBalanceWarning> m_balanceWarning;
0384 };
0385 
0386 #endif