1 /*
2  * Copyright 2007-2018  Thomas Baumgart <tbaumgart@kde.org>
3  * Copyright 2017-2018  Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of
8  * the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "keditscheduledlg.h"
20 
21 // ----------------------------------------------------------------------------
22 // QT Includes
23 
24 #include <QTimer>
25 #include <QCheckBox>
26 #include <QLabel>
27 #include <QList>
28 #include <QPushButton>
29 
30 // ----------------------------------------------------------------------------
31 // KDE Includes
32 
33 #include <KStandardGuiItem>
34 #include <KLineEdit>
35 #include <KHelpClient>
36 #include <KMessageBox>
37 #include <KLocalizedString>
38 
39 // ----------------------------------------------------------------------------
40 // Project Includes
41 
42 #include "ui_keditscheduledlg.h"
43 
44 #include "tabbar.h"
45 #include "mymoneyexception.h"
46 #include "mymoneyfile.h"
47 #include "mymoneyaccount.h"
48 #include "mymoneymoney.h"
49 #include "mymoneyschedule.h"
50 #include "mymoneysplit.h"
51 #include "mymoneytransaction.h"
52 #include "register.h"
53 #include "transactionform.h"
54 #include "transaction.h"
55 #include "selectedtransactions.h"
56 #include "transactioneditor.h"
57 #include "kmymoneylineedit.h"
58 #include "kmymoneydateinput.h"
59 #include "kmymoneymvccombo.h"
60 #include "kguiutils.h"
61 #include "kmymoneyutils.h"
62 #include "knewaccountdlg.h"
63 #include "knewinvestmentwizard.h"
64 #include "keditloanwizard.h"
65 #include "kmymoneysettings.h"
66 #include "mymoneyenums.h"
67 #include "widgetenums.h"
68 
69 using namespace eMyMoney;
70 
71 class KEditScheduleDlgPrivate
72 {
73   Q_DISABLE_COPY(KEditScheduleDlgPrivate)
74   Q_DECLARE_PUBLIC(KEditScheduleDlg)
75 
76 public:
KEditScheduleDlgPrivate(KEditScheduleDlg * qq)77   explicit KEditScheduleDlgPrivate(KEditScheduleDlg *qq) :
78     q_ptr(qq),
79     ui(new Ui::KEditScheduleDlg),
80     m_item(nullptr),
81     m_editor(nullptr),
82     m_requiredFields(nullptr)
83   {
84   }
85 
~KEditScheduleDlgPrivate()86   ~KEditScheduleDlgPrivate()
87   {
88     delete ui;
89   }
90 
init()91   void init()
92   {
93     Q_Q(KEditScheduleDlg);
94     ui->setupUi(q);
95 
96     m_requiredFields = new KMandatoryFieldGroup(q);
97     m_requiredFields->setOkButton(ui->buttonBox->button(QDialogButtonBox::Ok)); // button to be enabled when all fields present
98 
99     // make sure, we have a tabbar with the form
100     // insert it after the horizontal line
101     ui->m_paymentInformationLayout->insertWidget(2, ui->m_form->getTabBar(ui->m_form->parentWidget()));
102 
103     // we never need to see the register
104     ui->m_register->hide();
105 
106     // ... setup the form ...
107     ui->m_form->setupForm(m_schedule.account());
108 
109     // ... and the register ...
110     ui->m_register->clear();
111 
112     // ... now add the transaction to register and form ...
113     auto t = transaction();
114     if (m_schedule.transaction().splits().isEmpty())
115       m_item = KMyMoneyRegister::Register::transactionFactory(ui->m_register, t, MyMoneySplit(), 0);
116     else
117       m_item = KMyMoneyRegister::Register::transactionFactory(ui->m_register, t,
118                   m_schedule.transaction().splits().isEmpty() ? MyMoneySplit() : m_schedule.transaction().splits().front(), 0);
119     ui->m_register->selectItem(m_item);
120     // show the account row
121     m_item->setShowRowInForm(0, true);
122 
123     ui->m_form->slotSetTransaction(m_item);
124 
125     // setup widget contents
126     ui->m_nameEdit->setText(m_schedule.name());
127 
128     ui->m_frequencyEdit->setCurrentItem((int)m_schedule.occurrence());
129     if (ui->m_frequencyEdit->currentItem() == Schedule::Occurrence::Any)
130       ui->m_frequencyEdit->setCurrentItem((int)Schedule::Occurrence::Monthly);
131     q->slotFrequencyChanged((int)ui->m_frequencyEdit->currentItem());
132     ui->m_frequencyNoEdit->setValue(m_schedule.occurrenceMultiplier());
133 
134     // load option widgets
135     ui->m_paymentMethodEdit->insertItem(i18n("Direct deposit"), (int)Schedule::PaymentType::DirectDeposit);
136     ui->m_paymentMethodEdit->insertItem(i18n("Manual deposit"), (int)Schedule::PaymentType::ManualDeposit);
137     ui->m_paymentMethodEdit->insertItem(i18n("Direct debit"), (int)Schedule::PaymentType::DirectDebit);
138     ui->m_paymentMethodEdit->insertItem(i18n("Standing order"), (int)Schedule::PaymentType::StandingOrder);
139     ui->m_paymentMethodEdit->insertItem(i18n("Bank transfer"), (int)Schedule::PaymentType::BankTransfer);
140     ui->m_paymentMethodEdit->insertItem(i18n("Write check"), (int)Schedule::PaymentType::WriteChecque);
141     ui->m_paymentMethodEdit->insertItem(i18nc("Other payment method", "Other"), (int)Schedule::PaymentType::Other);
142 
143     auto method = m_schedule.paymentType();
144     if (method == Schedule::PaymentType::Any)
145       method = Schedule::PaymentType::Other;
146     ui->m_paymentMethodEdit->setCurrentItem((int)method);
147 
148     switch (m_schedule.weekendOption()) {
149       case Schedule::WeekendOption::MoveNothing:
150         ui->m_weekendOptionEdit->setCurrentIndex(0);
151         break;
152       case Schedule::WeekendOption::MoveBefore:
153         ui->m_weekendOptionEdit->setCurrentIndex(1);
154         break;
155       case Schedule::WeekendOption::MoveAfter:
156         ui->m_weekendOptionEdit->setCurrentIndex(2);
157         break;
158     }
159     ui->m_estimateEdit->setChecked(!m_schedule.isFixed());
160     ui->m_lastDayInMonthEdit->setChecked(m_schedule.lastDayInMonth());
161     ui->m_autoEnterEdit->setChecked(m_schedule.autoEnter());
162     ui->m_endSeriesEdit->setChecked(m_schedule.willEnd());
163 
164     ui->m_endOptionsFrame->setEnabled(m_schedule.willEnd());
165     if (m_schedule.willEnd()) {
166       ui->m_RemainingEdit->setValue(m_schedule.transactionsRemaining());
167       ui->m_FinalPaymentEdit->setDate(m_schedule.endDate());
168     }
169 
170     q->connect(ui->m_RemainingEdit, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
171             q, &KEditScheduleDlg::slotRemainingChanged);
172     q->connect(ui->m_FinalPaymentEdit, &KMyMoneyDateInput::dateChanged,
173             q, &KEditScheduleDlg::slotEndDateChanged);
174     q->connect(ui->m_frequencyEdit, &KMyMoneyGeneralCombo::itemSelected,
175             q, &KEditScheduleDlg::slotFrequencyChanged);
176     q->connect(ui->m_frequencyNoEdit, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
177             q, &KEditScheduleDlg::slotOccurrenceMultiplierChanged);
178     q->connect(ui->buttonBox, &QDialogButtonBox::helpRequested, q, &KEditScheduleDlg::slotShowHelp);
179 
180     q->setModal(true);
181     // force the initial height to be as small as possible
182     QTimer::singleShot(0, q, SLOT(slotSetupSize()));
183 
184     // we just hide the variation field for now and enable the logic
185     // once we have a respective member in the MyMoneySchedule object
186     ui->m_variation->hide();
187   }
188 
189   /**
190     * Helper method to recalculate and update Transactions Remaining
191     * when other values are changed
192     */
updateTransactionsRemaining()193   void updateTransactionsRemaining()
194   {
195     auto remain = m_schedule.transactionsRemaining();
196     if (remain != ui->m_RemainingEdit->value()) {
197       ui->m_RemainingEdit->blockSignals(true);
198       ui->m_RemainingEdit->setValue(remain);
199       ui->m_RemainingEdit->blockSignals(false);
200     }
201   }
202 
transaction() const203   MyMoneyTransaction transaction() const
204   {
205     auto t = m_schedule.transaction();
206 
207     if (m_editor) {
208       m_editor->createTransaction(t, m_schedule.transaction(), m_schedule.transaction().splits().isEmpty() ? MyMoneySplit() : m_schedule.transaction().splits().front(), false);
209     }
210 
211     t.clearId();
212     t.setEntryDate(QDate());
213     return t;
214   }
215 
216   KEditScheduleDlg              *q_ptr;
217   Ui::KEditScheduleDlg          *ui;
218   MyMoneySchedule                m_schedule;
219   KMyMoneyRegister::Transaction* m_item;
220   QWidgetList                    m_tabOrderWidgets;
221   TransactionEditor*             m_editor;
222   KMandatoryFieldGroup*          m_requiredFields;
223 };
224 
KEditScheduleDlg(const MyMoneySchedule & schedule,QWidget * parent)225 KEditScheduleDlg::KEditScheduleDlg(const MyMoneySchedule& schedule, QWidget *parent) :
226   QDialog(parent),
227   d_ptr(new KEditScheduleDlgPrivate(this))
228 {
229   Q_D(KEditScheduleDlg);
230   d->m_schedule = schedule;
231   d->m_editor = 0;
232   d->init();
233 }
234 
~KEditScheduleDlg()235 KEditScheduleDlg::~KEditScheduleDlg()
236 {
237   Q_D(KEditScheduleDlg);
238   delete d;
239 }
240 
slotSetupSize()241 void KEditScheduleDlg::slotSetupSize()
242 {
243   resize(width(), minimumSizeHint().height());
244 }
245 
startEdit()246 TransactionEditor* KEditScheduleDlg::startEdit()
247 {
248   Q_D(KEditScheduleDlg);
249   KMyMoneyRegister::SelectedTransactions list(d->ui->m_register);
250   TransactionEditor* editor = d->m_item->createEditor(d->ui->m_form, list, QDate());
251 
252   // check that we use the same transaction commodity in all selected transactions
253   // if not, we need to update this in the editor's list. The user can also bail out
254   // of this operation which means that we have to stop editing here.
255   if (editor && !d->m_schedule.account().id().isEmpty()) {
256     if (!editor->fixTransactionCommodity(d->m_schedule.account())) {
257       // if the user wants to quit, we need to destroy the editor
258       // and bail out
259       delete editor;
260       editor = 0;
261     }
262   }
263 
264   if (editor) {
265     editor->setScheduleInfo(d->ui->m_nameEdit->text());
266     connect(editor, &TransactionEditor::transactionDataSufficient, d->m_requiredFields, &KMandatoryFieldGroup::setExternalMandatoryState);
267     connect(editor, &TransactionEditor::escapePressed, d->ui->buttonBox->button(QDialogButtonBox::Cancel), &QAbstractButton::animateClick);
268     connect(editor, &TransactionEditor::returnPressed, d->ui->buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::animateClick);
269 
270     connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, editor, &TransactionEditor::slotReloadEditWidgets);
271     // connect(editor, SIGNAL(finishEdit(KMyMoneyRegister::SelectedTransactions)), this, SLOT(slotLeaveEditMode(KMyMoneyRegister::SelectedTransactions)));
272     connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, editor, &TransactionEditor::slotReloadEditWidgets);
273 
274     // create the widgets, place them in the parent and load them with data
275     // setup tab order
276     d->m_tabOrderWidgets.clear();
277     eWidgets::eRegister::Action action = eWidgets::eRegister::Action::Withdrawal;
278     switch (d->m_schedule.type()) {
279       case Schedule::Type::Deposit:
280         action = eWidgets::eRegister::Action::Deposit;
281         break;
282       case Schedule::Type::Bill:
283         action = eWidgets::eRegister::Action::Withdrawal;
284         editor->setPaymentMethod(d->m_schedule.paymentType());
285         break;
286       case Schedule::Type::Transfer:
287         action = eWidgets::eRegister::Action::Transfer;
288         break;
289       default:
290         // if we end up here, we don't have a known schedule type (yet). in this case, we just glimpse
291         // into the transaction and determine the type. in case we don't have a transaction with splits
292         // we stick with the default action already set up
293         if (d->m_schedule.transaction().splits().count() > 0) {
294           auto isDeposit = false;
295           auto isTransfer = false;
296           auto splits = d->m_schedule.transaction().splits();
297           foreach (const auto split, splits) {
298             if (split.accountId() == d->m_schedule.account().id()) {
299               isDeposit = !(split.shares().isNegative());
300             } else {
301               auto acc = MyMoneyFile::instance()->account(split.accountId());
302               if (acc.isAssetLiability() && d->m_schedule.transaction().splits().count() == 2) {
303                 isTransfer = true;
304               }
305             }
306           }
307 
308           if (isTransfer)
309             action = eWidgets::eRegister::Action::Transfer;
310           else if (isDeposit)
311             action = eWidgets::eRegister::Action::Deposit;
312         }
313         break;
314     }
315     editor->setup(d->m_tabOrderWidgets, d->m_schedule.account(), action);
316 
317     // if it's not a check, then we need to clear
318     // a possibly assigned check number
319     if (d->m_schedule.paymentType() != Schedule::PaymentType::WriteChecque) {
320       QWidget* w = editor->haveWidget("number");
321       if (w) {
322         if (auto numberWidget = dynamic_cast<KMyMoneyLineEdit*>(w)) {
323           numberWidget->loadText(QString());
324         }
325       }
326     }
327 
328     Q_ASSERT(!d->m_tabOrderWidgets.isEmpty());
329 
330     d->m_tabOrderWidgets.push_front(d->ui->m_paymentMethodEdit);
331 
332     // editor->setup() leaves the tabbar as the last widget in the stack, but we
333     // need it as first here. So we move it around.
334     QWidget* w = editor->haveWidget("tabbar");
335     if (w) {
336       int idx = d->m_tabOrderWidgets.indexOf(w);
337       if (idx != -1) {
338         d->m_tabOrderWidgets.removeAt(idx);
339         d->m_tabOrderWidgets.push_front(w);
340       }
341     }
342 
343     // don't forget our three buttons and additional widgets
344     // make sure to use the correct order
345     d->m_tabOrderWidgets.push_front(d->ui->m_frequencyEdit);
346     d->m_tabOrderWidgets.push_front(d->ui->m_frequencyNoEdit);
347     d->m_tabOrderWidgets.push_front(d->ui->m_nameEdit);
348 
349     d->m_tabOrderWidgets.append(d->ui->m_weekendOptionEdit);
350     d->m_tabOrderWidgets.append(d->ui->m_estimateEdit);
351     d->m_tabOrderWidgets.append(d->ui->m_variation);
352     d->m_tabOrderWidgets.append(d->ui->m_lastDayInMonthEdit);
353     d->m_tabOrderWidgets.append(d->ui->m_autoEnterEdit);
354     d->m_tabOrderWidgets.append(d->ui->m_endSeriesEdit);
355     d->m_tabOrderWidgets.append(d->ui->m_RemainingEdit);
356     d->m_tabOrderWidgets.append(d->ui->m_FinalPaymentEdit);
357 
358     d->m_tabOrderWidgets.append(d->ui->buttonBox->button(QDialogButtonBox::Ok));
359     d->m_tabOrderWidgets.append(d->ui->buttonBox->button(QDialogButtonBox::Cancel));
360     d->m_tabOrderWidgets.append(d->ui->buttonBox->button(QDialogButtonBox::Help));
361     for (auto i = 0; i < d->m_tabOrderWidgets.size(); ++i) {
362       w = d->m_tabOrderWidgets.at(i);
363       if (w) {
364         w->installEventFilter(this);
365         w->installEventFilter(editor);
366       }
367     }
368 
369     // connect the postdate modification signal to our update routine
370     if (auto dateEdit = dynamic_cast<KMyMoneyDateInput*>(editor->haveWidget("postdate")))
371       connect(dateEdit, &KMyMoneyDateInput::dateChanged, this, &KEditScheduleDlg::slotPostDateChanged);
372 
373     d->ui->m_nameEdit->setFocus();
374 
375     // add the required fields to the mandatory group
376     d->m_requiredFields->add(d->ui->m_nameEdit);
377     d->m_requiredFields->add(editor->haveWidget("account"));
378     d->m_requiredFields->add(editor->haveWidget("category"));
379 
380     // fix labels
381     if (auto label = dynamic_cast<QLabel*>(editor->haveWidget("date-label")))
382       label->setText(i18n("Next due date"));
383 
384     d->m_editor = editor;
385     slotSetPaymentMethod((int)d->m_schedule.paymentType());
386 
387     connect(d->ui->m_paymentMethodEdit, &KMyMoneyGeneralCombo::itemSelected, this, &KEditScheduleDlg::slotSetPaymentMethod);
388     connect(editor, &TransactionEditor::operationTypeChanged, this, &KEditScheduleDlg::slotFilterPaymentType);
389   }
390 
391   return editor;
392 }
393 
accept()394 void KEditScheduleDlg::accept()
395 {
396   Q_D(KEditScheduleDlg);
397   // Force the focus to be on the OK button. This will trigger creation
398   // of any unknown objects (payees, categories etc.)
399   d->ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus();
400 
401   // only accept if the button is really still enabled. We could end
402   // up here, if the user filled all fields, the focus is on the category
403   // field, but the category is not yet existent. When the user presses the
404   // OK button in this context, he will be asked if he wants to create
405   // the category or not. In case he decides no, we end up here with no
406   // category filled in, so we don't run through the final acceptance.
407   if (d->ui->buttonBox->button(QDialogButtonBox::Ok)->isEnabled())
408     QDialog::accept();
409 }
410 
schedule()411 const MyMoneySchedule& KEditScheduleDlg::schedule()
412 {
413   Q_D(KEditScheduleDlg);
414   if (d->m_editor) {
415     auto t = d->transaction();
416     if (d->m_schedule.nextDueDate() != t.postDate()) {
417       d->m_schedule.setNextDueDate(t.postDate());
418       d->m_schedule.setStartDate(t.postDate());
419     }
420     d->m_schedule.setTransaction(t);
421     d->m_schedule.setName(d->ui->m_nameEdit->text());
422     d->m_schedule.setFixed(!d->ui->m_estimateEdit->isChecked());
423     d->m_schedule.setOccurrencePeriod(static_cast<Schedule::Occurrence>(d->ui->m_frequencyEdit->currentItem()));
424     d->m_schedule.setOccurrenceMultiplier(d->ui->m_frequencyNoEdit->value());
425 
426     switch (d->ui->m_weekendOptionEdit->currentIndex())  {
427       case 0:
428         d->m_schedule.setWeekendOption(Schedule::WeekendOption::MoveNothing);
429         break;
430       case 1:
431         d->m_schedule.setWeekendOption(Schedule::WeekendOption::MoveBefore);
432         break;
433       case 2:
434         d->m_schedule.setWeekendOption(Schedule::WeekendOption::MoveAfter);
435         break;
436     }
437 
438     d->m_schedule.setType(Schedule::Type::Bill);
439 
440     if (auto tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(d->m_editor->haveWidget("tabbar"))) {
441       switch (static_cast<eWidgets::eRegister::Action>(tabbar->currentIndex())) {
442         case eWidgets::eRegister::Action::Deposit:
443           d->m_schedule.setType(Schedule::Type::Deposit);
444           break;
445         default:
446         case eWidgets::eRegister::Action::Withdrawal:
447           d->m_schedule.setType(Schedule::Type::Bill);
448           break;
449         case eWidgets::eRegister::Action::Transfer:
450           d->m_schedule.setType(Schedule::Type::Transfer);
451           break;
452       }
453     } else {
454       qDebug("No tabbar found in KEditScheduleDlg::schedule(). Defaulting type to BILL");
455     }
456 
457     if(d->ui->m_lastDayInMonthEdit->isEnabled())
458       d->m_schedule.setLastDayInMonth(d->ui->m_lastDayInMonthEdit->isChecked());
459     else
460       d->m_schedule.setLastDayInMonth(false);
461     d->m_schedule.setAutoEnter(d->ui->m_autoEnterEdit->isChecked());
462     d->m_schedule.setPaymentType(static_cast<Schedule::PaymentType>(d->ui->m_paymentMethodEdit->currentItem()));
463     if (d->ui->m_endSeriesEdit->isEnabled() && d->ui->m_endSeriesEdit->isChecked()) {
464       d->m_schedule.setEndDate(d->ui->m_FinalPaymentEdit->date());
465     } else {
466       d->m_schedule.setEndDate(QDate());
467     }
468   }
469   return d->m_schedule;
470 }
471 
newSchedule(const MyMoneyTransaction & _t,eMyMoney::Schedule::Occurrence occurrence)472 void KEditScheduleDlg::newSchedule(const MyMoneyTransaction& _t, eMyMoney::Schedule::Occurrence occurrence)
473 {
474   MyMoneySchedule schedule;
475   schedule.setOccurrence(occurrence);
476 
477   // if the schedule is based on an existing transaction,
478   // we take the post date and project it to the next
479   // schedule in a month.
480   if (_t != MyMoneyTransaction()) {
481     MyMoneyTransaction t(_t);
482     schedule.setTransaction(t);
483     if (occurrence != eMyMoney::Schedule::Occurrence::Once)
484       schedule.setNextDueDate(schedule.nextPayment(t.postDate()));
485   }
486 
487   bool committed;
488   do {
489     committed = true;
490     QPointer<KEditScheduleDlg> dlg = new KEditScheduleDlg(schedule, nullptr);
491     QPointer<TransactionEditor> transactionEditor = dlg->startEdit();
492     KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart());
493     if (dlg->exec() == QDialog::Accepted && dlg != 0) {
494       MyMoneyFileTransaction ft;
495       try {
496         schedule = dlg->schedule();
497         MyMoneyFile::instance()->addSchedule(schedule);
498         ft.commit();
499 
500       } catch (const MyMoneyException &e) {
501         KMessageBox::error(nullptr, i18n("Unable to add scheduled transaction: %1", QString::fromLatin1(e.what())), i18n("Add scheduled transaction"));
502         committed = false;
503       }
504     }
505     delete transactionEditor;
506     delete dlg;
507   } while(!committed);
508 }
509 
editSchedule(const MyMoneySchedule & inputSchedule)510 void KEditScheduleDlg::editSchedule(const MyMoneySchedule& inputSchedule)
511 {
512     try {
513       auto schedule = MyMoneyFile::instance()->schedule(inputSchedule.id());
514 
515       KEditScheduleDlg* sched_dlg = nullptr;
516       KEditLoanWizard* loan_wiz = nullptr;
517 
518       switch (schedule.type()) {
519         case eMyMoney::Schedule::Type::Bill:
520         case eMyMoney::Schedule::Type::Deposit:
521         case eMyMoney::Schedule::Type::Transfer:
522         {
523           sched_dlg = new KEditScheduleDlg(schedule, nullptr);
524           QPointer<TransactionEditor> transactionEditor = sched_dlg->startEdit();
525           if (transactionEditor) {
526             KMyMoneyMVCCombo::setSubstringSearchForChildren(sched_dlg, !KMyMoneySettings::stringMatchFromStart());
527             if (sched_dlg->exec() == QDialog::Accepted) {
528               MyMoneyFileTransaction ft;
529               try {
530                 MyMoneySchedule sched = sched_dlg->schedule();
531                 // Check whether the new Schedule Date
532                 // is at or before the lastPaymentDate
533                 // If it is, ask the user whether to clear the
534                 // lastPaymentDate
535                 const auto& next = sched.nextDueDate();
536                 const auto& last = sched.lastPayment();
537                 if (next.isValid() && last.isValid() && next <= last) {
538                   // Entered a date effectively no later
539                   // than previous payment.  Date would be
540                   // updated automatically so we probably
541                   // want to clear it.  Let's ask the user.
542                   if (KMessageBox::questionYesNo(nullptr, i18n("<qt>You have entered a scheduled transaction date of <b>%1</b>.  Because the scheduled transaction was last paid on <b>%2</b>, KMyMoney will automatically adjust the scheduled transaction date to the next date unless the last payment date is reset.  Do you want to reset the last payment date?</qt>", QLocale().toString(next, QLocale::ShortFormat), QLocale().toString(last, QLocale::ShortFormat)), i18n("Reset Last Payment Date"), KStandardGuiItem::yes(), KStandardGuiItem::no()) == KMessageBox::Yes) {
543                     sched.setLastPayment(QDate());
544                   }
545                 }
546                 MyMoneyFile::instance()->modifySchedule(sched);
547                 // delete the editor before we emit the dataChanged() signal from the
548                 // engine. Calling this twice in a row does not hurt.
549                 delete transactionEditor;
550                 ft.commit();
551               } catch (const MyMoneyException &e) {
552                 KMessageBox::detailedSorry(nullptr, i18n("Unable to modify scheduled transaction '%1'", inputSchedule.name()), QString::fromLatin1(e.what()));
553               }
554             }
555             delete transactionEditor;
556           }
557           delete sched_dlg;
558           break;
559         }
560         case eMyMoney::Schedule::Type::LoanPayment:
561         {
562           loan_wiz = new KEditLoanWizard(schedule.account(2));
563           if (loan_wiz->exec() == QDialog::Accepted) {
564             MyMoneyFileTransaction ft;
565             try {
566               MyMoneyFile::instance()->modifySchedule(loan_wiz->schedule());
567               MyMoneyFile::instance()->modifyAccount(loan_wiz->account());
568               ft.commit();
569             } catch (const MyMoneyException &e) {
570               KMessageBox::detailedSorry(nullptr, i18n("Unable to modify scheduled transaction '%1'", inputSchedule.name()), QString::fromLatin1(e.what()));
571             }
572           }
573           delete loan_wiz;
574           break;
575         }
576         case eMyMoney::Schedule::Type::Any:
577           break;
578       }
579 
580     } catch (const MyMoneyException &e) {
581       KMessageBox::detailedSorry(nullptr, i18n("Unable to modify scheduled transaction '%1'", inputSchedule.name()), QString::fromLatin1(e.what()));
582     }
583 }
584 
focusNextPrevChild(bool next)585 bool KEditScheduleDlg::focusNextPrevChild(bool next)
586 {
587   Q_D(KEditScheduleDlg);
588   auto rc = false;
589 
590   auto w = qApp->focusWidget();
591   auto currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w);
592   while (w && currentWidgetIndex == -1) {
593     // qDebug("'%s' not in list, use parent", qPrintable(w->objectName()));
594     w = w->parentWidget();
595     currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w);
596   }
597 
598   if (currentWidgetIndex != -1) {
599     do {
600       // if(w) qDebug("tab order is at '%s (%d/%d)'", qPrintable(w->objectName()), currentWidgetIndex, d->m_tabOrderWidgets.size());
601       currentWidgetIndex += next ? 1 : -1;
602       if (currentWidgetIndex < 0)
603         currentWidgetIndex = d->m_tabOrderWidgets.size() - 1;
604       else if (currentWidgetIndex >= d->m_tabOrderWidgets.size())
605         currentWidgetIndex = 0;
606 
607       w = d->m_tabOrderWidgets[currentWidgetIndex];
608       // qDebug("currentWidgetIndex = %d, w = %p", currentWidgetIndex, w);
609 
610       if (((w->focusPolicy() & Qt::TabFocus) == Qt::TabFocus) && w->isVisible() && w->isEnabled()) {
611         // qDebug("Selecting '%s' as focus", qPrintable(w->objectName()));
612         w->setFocus(next ? Qt::TabFocusReason: Qt::BacktabFocusReason);
613         rc = true;
614       }
615     } while (rc == false);
616   }
617   return rc;
618 }
619 
resizeEvent(QResizeEvent * ev)620 void KEditScheduleDlg::resizeEvent(QResizeEvent* ev)
621 {
622   Q_D(KEditScheduleDlg);
623   d->ui->m_register->resize((int)eWidgets::eTransaction::Column::Detail);
624   d->ui->m_form->resize((int)eWidgets::eTransactionForm::Column::Value1);
625   QDialog::resizeEvent(ev);
626 }
627 
628 
slotRemainingChanged(int value)629 void KEditScheduleDlg::slotRemainingChanged(int value)
630 {
631   Q_D(KEditScheduleDlg);
632   // Make sure the required fields are set
633   if (auto dateEdit = dynamic_cast<KMyMoneyDateInput*>(d->m_editor->haveWidget("postdate")))
634     d->m_schedule.setNextDueDate(dateEdit->date());
635   d->m_schedule.setOccurrencePeriod(static_cast<Schedule::Occurrence>(d->ui->m_frequencyEdit->currentItem()));
636   d->m_schedule.setOccurrenceMultiplier(d->ui->m_frequencyNoEdit->value());
637 
638   if (d->m_schedule.transactionsRemaining() != value) {
639     d->ui->m_FinalPaymentEdit->blockSignals(true);
640     d->ui->m_FinalPaymentEdit->setDate(d->m_schedule.dateAfter(value));
641     d->ui->m_FinalPaymentEdit->blockSignals(false);
642   }
643 }
644 
slotEndDateChanged(const QDate & date)645 void KEditScheduleDlg::slotEndDateChanged(const QDate& date)
646 {
647   Q_D(KEditScheduleDlg);
648   // Make sure the required fields are set
649   if (auto dateEdit = dynamic_cast<KMyMoneyDateInput*>(d->m_editor->haveWidget("postdate")))
650     d->m_schedule.setNextDueDate(dateEdit->date());
651   d->m_schedule.setOccurrencePeriod(static_cast<Schedule::Occurrence>(d->ui->m_frequencyEdit->currentItem()));
652   d->m_schedule.setOccurrenceMultiplier(d->ui->m_frequencyNoEdit->value());
653 
654   if (d->m_schedule.endDate() != date) {
655     d->m_schedule.setEndDate(date);
656     d->updateTransactionsRemaining();
657   }
658 }
659 
slotPostDateChanged(const QDate & date)660 void KEditScheduleDlg::slotPostDateChanged(const QDate& date)
661 {
662   Q_D(KEditScheduleDlg);
663   if (d->m_schedule.nextDueDate() != date) {
664     if (d->ui->m_endOptionsFrame->isEnabled()) {
665       d->m_schedule.setNextDueDate(date);
666       d->m_schedule.setStartDate(date);
667       d->m_schedule.setOccurrenceMultiplier(d->ui->m_frequencyNoEdit->value());
668       d->m_schedule.setOccurrencePeriod(static_cast<Schedule::Occurrence>(d->ui->m_frequencyEdit->currentItem()));
669       d->m_schedule.setEndDate(d->ui->m_FinalPaymentEdit->date());
670       d->updateTransactionsRemaining();
671     }
672   }
673 }
674 
slotSetPaymentMethod(int item)675 void KEditScheduleDlg::slotSetPaymentMethod(int item)
676 {
677   Q_D(KEditScheduleDlg);
678   const bool isWriteCheck = item == (int)Schedule::PaymentType::WriteChecque;
679   if (auto numberEdit = dynamic_cast<KMyMoneyLineEdit*>(d->m_editor->haveWidget("number"))) {
680     numberEdit->setVisible(isWriteCheck);
681 
682     // hiding the label does not work, because the label underneath will shine
683     // through. So we either write the label or a blank
684     if (auto label = dynamic_cast<QLabel *>(d->m_editor->haveWidget("number-label")))
685       label->setText(isWriteCheck ? i18n("Number") : QStringLiteral(" "));
686   }
687 }
688 
slotFrequencyChanged(int item)689 void KEditScheduleDlg::slotFrequencyChanged(int item)
690 {
691   Q_D(KEditScheduleDlg);
692   d->ui->m_endSeriesEdit->setEnabled(item != (int)Schedule::Occurrence::Once);
693   bool isEndSeries = d->ui->m_endSeriesEdit->isChecked();
694   if (isEndSeries)
695     d->ui->m_endOptionsFrame->setEnabled(item != (int)Schedule::Occurrence::Once);
696   switch (item) {
697     case (int)Schedule::Occurrence::Daily:
698     case (int)Schedule::Occurrence::Weekly:
699       d->ui->m_frequencyNoEdit->setEnabled(true);
700       d->ui->m_lastDayInMonthEdit->setEnabled(false);
701       break;
702 
703     case (int)Schedule::Occurrence::EveryHalfMonth:
704     case (int)Schedule::Occurrence::Monthly:
705     case (int)Schedule::Occurrence::Yearly:
706       // Supports Frequency Number
707       d->ui->m_frequencyNoEdit->setEnabled(true);
708       d->ui->m_lastDayInMonthEdit->setEnabled(true);
709       break;
710 
711     default:
712       // Multiplier is always 1
713       d->ui->m_frequencyNoEdit->setEnabled(false);
714       d->ui->m_frequencyNoEdit->setValue(1);
715       d->ui->m_lastDayInMonthEdit->setEnabled(true);
716       break;
717   }
718   if (isEndSeries && (item != (int)Schedule::Occurrence::Once)) {
719     // Changing the frequency changes the number
720     // of remaining transactions
721     if (auto dateEdit = dynamic_cast<KMyMoneyDateInput*>(d->m_editor->haveWidget("postdate")))
722       d->m_schedule.setNextDueDate(dateEdit->date());
723     d->m_schedule.setOccurrenceMultiplier(d->ui->m_frequencyNoEdit->value());
724     d->m_schedule.setOccurrencePeriod(static_cast<Schedule::Occurrence>(item));
725     d->m_schedule.setEndDate(d->ui->m_FinalPaymentEdit->date());
726     d->updateTransactionsRemaining();
727   }
728 }
729 
slotOccurrenceMultiplierChanged(int multiplier)730 void KEditScheduleDlg::slotOccurrenceMultiplierChanged(int multiplier)
731 {
732   Q_D(KEditScheduleDlg);
733   // Make sure the required fields are set
734   auto oldOccurrenceMultiplier = d->m_schedule.occurrenceMultiplier();
735   if (multiplier != oldOccurrenceMultiplier) {
736     if (d->ui->m_endOptionsFrame->isEnabled()) {
737       if (auto dateEdit = dynamic_cast<KMyMoneyDateInput*>(d->m_editor->haveWidget("postdate")))
738         d->m_schedule.setNextDueDate(dateEdit->date());
739       d->m_schedule.setOccurrenceMultiplier(multiplier);
740       d->m_schedule.setOccurrencePeriod(static_cast<Schedule::Occurrence>(d->ui->m_frequencyEdit->currentItem()));
741       d->m_schedule.setEndDate(d->ui->m_FinalPaymentEdit->date());
742       d->updateTransactionsRemaining();
743     }
744   }
745 }
746 
slotShowHelp()747 void KEditScheduleDlg::slotShowHelp()
748 {
749   KHelpClient::invokeHelp("details.schedules.intro");
750 }
751 
slotFilterPaymentType(int index)752 void KEditScheduleDlg::slotFilterPaymentType(int index)
753 {
754   Q_D(KEditScheduleDlg);
755   //save selected item to reload if possible
756   auto selectedId = d->ui->m_paymentMethodEdit->itemData(d->ui->m_paymentMethodEdit->currentIndex(), Qt::UserRole).toInt();
757 
758   //clear and reload the widget with the correct items
759   d->ui->m_paymentMethodEdit->clear();
760 
761   // load option widgets
762   eWidgets::eRegister::Action action = static_cast<eWidgets::eRegister::Action>(index);
763   if (action != eWidgets::eRegister::Action::Withdrawal) {
764     d->ui->m_paymentMethodEdit->insertItem(i18n("Direct deposit"), (int)Schedule::PaymentType::DirectDeposit);
765     d->ui->m_paymentMethodEdit->insertItem(i18n("Manual deposit"), (int)Schedule::PaymentType::ManualDeposit);
766   }
767   if (action != eWidgets::eRegister::Action::Deposit) {
768     d->ui->m_paymentMethodEdit->insertItem(i18n("Direct debit"), (int)Schedule::PaymentType::DirectDebit);
769     d->ui->m_paymentMethodEdit->insertItem(i18n("Write check"), (int)Schedule::PaymentType::WriteChecque);
770   }
771   d->ui->m_paymentMethodEdit->insertItem(i18n("Standing order"), (int)Schedule::PaymentType::StandingOrder);
772   d->ui->m_paymentMethodEdit->insertItem(i18n("Bank transfer"), (int)Schedule::PaymentType::BankTransfer);
773   d->ui->m_paymentMethodEdit->insertItem(i18nc("Other payment method", "Other"), (int)Schedule::PaymentType::Other);
774 
775   auto newIndex = d->ui->m_paymentMethodEdit->findData(QVariant(selectedId), Qt::UserRole, Qt::MatchExactly);
776   if (newIndex > -1) {
777     d->ui->m_paymentMethodEdit->setCurrentIndex(newIndex);
778   } else {
779     d->ui->m_paymentMethodEdit->setCurrentIndex(0);
780   }
781 
782 }
783