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