1 /*
2  * Copyright 2009-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 "stdtransactioneditor.h"
20 #include "transactioneditor_p.h"
21 
22 // ----------------------------------------------------------------------------
23 // QT Includes
24 
25 #include <QLabel>
26 #include <QApplication>
27 #include <QList>
28 #include <QPushButton>
29 
30 // ----------------------------------------------------------------------------
31 // KDE Includes
32 
33 #include <KTextEdit>
34 #include <KLocalizedString>
35 #include <KMessageBox>
36 #include <KStandardGuiItem>
37 
38 // ----------------------------------------------------------------------------
39 // Project Includes
40 
41 #include "kmymoneyreconcilecombo.h"
42 #include "kmymoneycashflowcombo.h"
43 #include "kmymoneypayeecombo.h"
44 #include "kmymoneytagcombo.h"
45 #include "ktagcontainer.h"
46 #include "tabbar.h"
47 #include "kmymoneycategory.h"
48 #include "kmymoneymvccombo.h"
49 #include "kmymoneydateinput.h"
50 #include "amountedit.h"
51 #include "kmymoneylineedit.h"
52 #include "kmymoneyaccountselector.h"
53 #include "mymoneyfile.h"
54 #include "mymoneypayee.h"
55 #include "mymoneytag.h"
56 #include "kmymoneyutils.h"
57 #include "kmymoneycompletion.h"
58 #include "transaction.h"
59 #include "transactionform.h"
60 #include "mymoneytransactionfilter.h"
61 #include "kmymoneysettings.h"
62 #include "transactioneditorcontainer.h"
63 
64 #include "ksplittransactiondlg.h"
65 #include "kcurrencycalculator.h"
66 #include "kselecttransactionsdlg.h"
67 #include "widgetenums.h"
68 
69 using namespace eWidgets;
70 using namespace KMyMoneyRegister;
71 using namespace KMyMoneyTransactionForm;
72 
73 class StdTransactionEditorPrivate : public TransactionEditorPrivate
74 {
75   Q_DISABLE_COPY(StdTransactionEditorPrivate)
76 
77 public:
StdTransactionEditorPrivate(StdTransactionEditor * qq)78   explicit StdTransactionEditorPrivate(StdTransactionEditor *qq) :
79     TransactionEditorPrivate(qq),
80     m_inUpdateVat(false)
81   {
82   }
83 
~StdTransactionEditorPrivate()84   ~StdTransactionEditorPrivate()
85   {
86   }
87 
88   MyMoneyMoney m_shares;
89   bool         m_inUpdateVat;
90 };
91 
StdTransactionEditor()92 StdTransactionEditor::StdTransactionEditor() :
93   TransactionEditor(*new StdTransactionEditorPrivate(this))
94 {
95 }
96 
StdTransactionEditor(TransactionEditorContainer * regForm,KMyMoneyRegister::Transaction * item,const KMyMoneyRegister::SelectedTransactions & list,const QDate & lastPostDate)97 StdTransactionEditor::StdTransactionEditor(TransactionEditorContainer* regForm,
98                                            KMyMoneyRegister::Transaction* item,
99                                            const KMyMoneyRegister::SelectedTransactions& list,
100                                            const QDate& lastPostDate) :
101     TransactionEditor(*new StdTransactionEditorPrivate(this),
102                       regForm,
103                       item,
104                       list,
105                       lastPostDate)
106 {
107 }
108 
~StdTransactionEditor()109 StdTransactionEditor::~StdTransactionEditor()
110 {
111 }
112 
createEditWidgets()113 void StdTransactionEditor::createEditWidgets()
114 {
115   Q_D(StdTransactionEditor);
116   // we only create the account widget in case it is needed
117   // to avoid confusion in the tab order later on.
118   if (d->m_item->showRowInForm(0)) {
119     auto account = new KMyMoneyCategory;
120     account->setPlaceholderText(i18n("Account"));
121     account->setObjectName(QLatin1String("Account"));
122     d->m_editWidgets["account"] = account;
123     connect(account, &QComboBox::editTextChanged, this, &StdTransactionEditor::slotUpdateButtonState);
124     connect(account, &KMyMoneyCombo::itemSelected, this, &StdTransactionEditor::slotUpdateAccount);
125   }
126 
127   auto payee = new KMyMoneyPayeeCombo;
128   payee->setPlaceholderText(i18n("Payer/Receiver"));
129   payee->setObjectName(QLatin1String("Payee"));
130   d->m_editWidgets["payee"] = payee;
131 
132   connect(payee, &KMyMoneyMVCCombo::createItem, this, &StdTransactionEditor::slotNewPayee);
133   connect(payee, &KMyMoneyMVCCombo::objectCreation, this, &StdTransactionEditor::objectCreation);
134   connect(payee, &KMyMoneyMVCCombo::itemSelected, this, &StdTransactionEditor::slotUpdatePayee);
135   connect(payee, &QComboBox::editTextChanged, this, &StdTransactionEditor::slotUpdateButtonState);
136 
137   auto category = new KMyMoneyCategory(true, nullptr);
138   category->setPlaceholderText(i18n("Category/Account"));
139   category->setObjectName(QLatin1String("Category/Account"));
140   d->m_editWidgets["category"] = category;
141   connect(category, &KMyMoneyCombo::itemSelected, this, &StdTransactionEditor::slotUpdateCategory);
142   connect(category, &QComboBox::editTextChanged, this, &StdTransactionEditor::slotUpdateButtonState);
143   connect(category, &KMyMoneyCombo::createItem, this, &StdTransactionEditor::slotCreateCategory);
144   connect(category, &KMyMoneyCombo::objectCreation, this, &StdTransactionEditor::objectCreation);
145   connect(category->splitButton(), &QAbstractButton::clicked, this, &StdTransactionEditor::slotEditSplits);
146   // initially disable the split button since we don't have an account set
147   if (category->splitButton())
148     category->splitButton()->setDisabled(d->m_account.id().isEmpty());
149 
150   auto tag = new KTagContainer;
151   tag->tagCombo()->setPlaceholderText(i18n("Tag"));
152   tag->tagCombo()->setObjectName(QLatin1String("Tag"));
153   d->m_editWidgets["tag"] = tag;
154   connect(tag->tagCombo(), &KMyMoneyMVCCombo::createItem, this, &StdTransactionEditor::slotNewTag);
155   connect(tag->tagCombo(), &KMyMoneyMVCCombo::objectCreation, this, &StdTransactionEditor::objectCreation);
156 
157   auto memo = new KTextEdit;
158   memo->setObjectName(QLatin1String("Memo"));
159   memo->setTabChangesFocus(true);
160   connect(memo, &QTextEdit::textChanged, this, &StdTransactionEditor::slotUpdateMemoState);
161   connect(memo, &QTextEdit::textChanged, this, &StdTransactionEditor::slotUpdateButtonState);
162   d->m_editWidgets["memo"] = memo;
163   d->m_memoText.clear();
164   d->m_memoChanged = false;
165 
166   bool showNumberField = true;
167   switch (d->m_account.accountType()) {
168     case eMyMoney::Account::Type::Savings:
169     case eMyMoney::Account::Type::Cash:
170     case eMyMoney::Account::Type::Loan:
171     case eMyMoney::Account::Type::AssetLoan:
172     case eMyMoney::Account::Type::Asset:
173     case eMyMoney::Account::Type::Liability:
174     case eMyMoney::Account::Type::Equity:
175       showNumberField = KMyMoneySettings::alwaysShowNrField();
176       break;
177 
178     case eMyMoney::Account::Type::Income:
179     case eMyMoney::Account::Type::Expense:
180       showNumberField = false;
181       break;
182 
183     default:
184       break;
185   }
186 
187   if (showNumberField) {
188     auto number = new KMyMoneyLineEdit;
189     number->setPlaceholderText(i18n("Number"));
190     number->setObjectName(QLatin1String("Number"));
191     d->m_editWidgets["number"] = number;
192     connect(number, &KMyMoneyLineEdit::lineChanged, this, &StdTransactionEditor::slotNumberChanged);
193     // number->installEventFilter(this);
194   }
195 
196   auto postDate = new KMyMoneyDateInput;
197   d->m_editWidgets["postdate"] = postDate;
198   postDate->setObjectName(QLatin1String("PostDate"));
199   connect(postDate, &KMyMoneyDateInput::dateChanged, this, &StdTransactionEditor::slotUpdateButtonState);
200   postDate->setDate(QDate());
201 
202   auto value = new AmountEdit;
203   d->m_editWidgets["amount"] = value;
204   value->setObjectName(QLatin1String("Amount"));
205   value->setCalculatorButtonVisible(true);
206   connect(value, &AmountEdit::valueChanged, this, &StdTransactionEditor::slotUpdateAmount);
207   connect(value, &AmountEdit::textChanged, this, &StdTransactionEditor::slotUpdateButtonState);
208 
209   value = new AmountEdit;
210   d->m_editWidgets["payment"] = value;
211   value->setObjectName(QLatin1String("Payment"));
212   value->setCalculatorButtonVisible(true);
213   connect(value, &AmountEdit::valueChanged, this, &StdTransactionEditor::slotUpdatePayment);
214   connect(value, &AmountEdit::textChanged, this, &StdTransactionEditor::slotUpdateButtonState);
215 
216   value = new AmountEdit;
217   d->m_editWidgets["deposit"] = value;
218   value->setObjectName(QLatin1String("Deposit"));
219   value->setCalculatorButtonVisible(true);
220   connect(value, &AmountEdit::valueChanged, this, &StdTransactionEditor::slotUpdateDeposit);
221   connect(value, &AmountEdit::textChanged, this, &StdTransactionEditor::slotUpdateButtonState);
222 
223   auto cashflow = new KMyMoneyCashFlowCombo(d->m_account.accountGroup(), nullptr);
224   d->m_editWidgets["cashflow"] = cashflow;
225   cashflow->setObjectName(QLatin1String("Cashflow"));
226   connect(cashflow, &KMyMoneyCashFlowCombo::directionSelected, this, &StdTransactionEditor::slotUpdateCashFlow);
227 
228   auto reconcile = new KMyMoneyReconcileCombo;
229   d->m_editWidgets["status"] = reconcile;
230   reconcile->setObjectName(QLatin1String("Reconcile"));
231 
232   KMyMoneyRegister::QWidgetContainer::iterator it_w;
233   for (it_w = d->m_editWidgets.begin(); it_w != d->m_editWidgets.end(); ++it_w) {
234     (*it_w)->installEventFilter(this);
235   }
236   // if we don't have more than 1 selected transaction, we don't need
237   // the "don't change" item in some of the combo widgets
238   if (!isMultiSelection()) {
239     reconcile->removeDontCare();
240     cashflow->removeDontCare();
241   }
242 
243   QLabel* label;
244   d->m_editWidgets["account-label"] = label = new QLabel(i18n("Account"));
245   label->setAlignment(Qt::AlignVCenter);
246 
247   d->m_editWidgets["category-label"] = label = new QLabel(i18n("Category"));
248   label->setAlignment(Qt::AlignVCenter);
249 
250   d->m_editWidgets["tag-label"] = label = new QLabel(i18n("Tags"));
251   label->setAlignment(Qt::AlignVCenter);
252 
253   d->m_editWidgets["memo-label"] = label = new QLabel(i18n("Memo"));
254   label->setAlignment(Qt::AlignVCenter);
255 
256   d->m_editWidgets["number-label"] = label = new QLabel(i18n("Number"));
257   label->setAlignment(Qt::AlignVCenter);
258 
259   d->m_editWidgets["date-label"] = label = new QLabel(i18n("Date"));
260   label->setAlignment(Qt::AlignVCenter);
261 
262   d->m_editWidgets["amount-label"] = label = new QLabel(i18n("Amount"));
263   label->setAlignment(Qt::AlignVCenter);
264 
265   d->m_editWidgets["status-label"] = label = new QLabel(i18n("Status"));
266   label->setAlignment(Qt::AlignVCenter);
267 
268   // create a copy of tabbar above the form (if we are created for a form)
269   auto form = dynamic_cast<KMyMoneyTransactionForm::TransactionForm*>(d->m_regForm);
270   if (form) {
271     auto tabbar = new KMyMoneyTransactionForm::TabBar;
272     d->m_editWidgets["tabbar"] = tabbar;
273     tabbar->setObjectName(QLatin1String("TabBar"));
274     tabbar->copyTabs(form->getTabBar());
275     connect(tabbar, &KMyMoneyTransactionForm::TabBar::tabCurrentChanged, this, &StdTransactionEditor::slotUpdateAction);
276     connect(tabbar, &KMyMoneyTransactionForm::TabBar::tabCurrentChanged, this, &TransactionEditor::operationTypeChanged);
277   }
278 
279   setupPrecision();
280 }
281 
setupCategoryWidget(QString & categoryId)282 void StdTransactionEditor::setupCategoryWidget(QString& categoryId)
283 {
284   Q_D(StdTransactionEditor);
285   if (auto categoryWidget = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"]))
286     TransactionEditor::setupCategoryWidget(categoryWidget, d->m_splits, categoryId, SLOT(slotEditSplits()));
287 
288   if (d->m_splits.count() == 1)
289     d->m_shares = d->m_splits[0].shares();
290 }
291 
isTransfer(const QString & accId1,const QString & accId2) const292 bool StdTransactionEditor::isTransfer(const QString& accId1, const QString& accId2) const
293 {
294   if (accId1.isEmpty() || accId2.isEmpty())
295     return false;
296 
297   return MyMoneyFile::instance()->account(accId1).isIncomeExpense() == MyMoneyFile::instance()->account(accId2).isIncomeExpense();
298 }
299 
loadEditWidgets(eRegister::Action action)300 void StdTransactionEditor::loadEditWidgets(eRegister::Action action)
301 {
302   Q_D(StdTransactionEditor);
303   // don't kick off VAT processing from here
304   d->m_inUpdateVat = true;
305 
306   QWidget* w;
307   AccountSet aSet;
308 
309   // load the account widget
310   if (auto account = dynamic_cast<KMyMoneyCategory*>(haveWidget("account"))) {
311     aSet.addAccountGroup(eMyMoney::Account::Type::Asset);
312     aSet.addAccountGroup(eMyMoney::Account::Type::Liability);
313     aSet.removeAccountType(eMyMoney::Account::Type::AssetLoan);
314     aSet.removeAccountType(eMyMoney::Account::Type::CertificateDep);
315     aSet.removeAccountType(eMyMoney::Account::Type::Investment);
316     aSet.removeAccountType(eMyMoney::Account::Type::Stock);
317     aSet.removeAccountType(eMyMoney::Account::Type::MoneyMarket);
318     aSet.removeAccountType(eMyMoney::Account::Type::Loan);
319     aSet.load(account->selector());
320     account->completion()->setSelected(d->m_account.id());
321     account->slotItemSelected(d->m_account.id());
322   }
323 
324   // load the payee widget
325   auto payee = dynamic_cast<KMyMoneyPayeeCombo*>(d->m_editWidgets["payee"]);
326   if (payee)
327     payee->loadPayees(MyMoneyFile::instance()->payeeList());
328 
329   // load the category widget
330   auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"]);
331 
332   if (category)
333     disconnect(category, &KMyMoneyCategory::focusIn, this, &StdTransactionEditor::slotEditSplits);
334 
335   // load the tag widget
336   //auto tag = dynamic_cast<KMyMoneyTagCombo*>(m_editWidgets["tag"]);
337   auto tag = dynamic_cast<KTagContainer*>(d->m_editWidgets["tag"]);
338   if (tag)
339     tag->loadTags(MyMoneyFile::instance()->tagList());
340 
341   // check if the current transaction has a reference to an equity account
342   auto haveEquityAccount = false;
343   foreach (const auto split, d->m_transaction.splits()) {
344     auto acc = MyMoneyFile::instance()->account(split.accountId());
345     if (acc.accountType() == eMyMoney::Account::Type::Equity) {
346       haveEquityAccount = true;
347       break;
348     }
349   }
350 
351   aSet.clear();
352   aSet.addAccountGroup(eMyMoney::Account::Type::Asset);
353   aSet.addAccountGroup(eMyMoney::Account::Type::Liability);
354   aSet.addAccountGroup(eMyMoney::Account::Type::Income);
355   aSet.addAccountGroup(eMyMoney::Account::Type::Expense);
356   if (KMyMoneySettings::expertMode() || haveEquityAccount)
357     aSet.addAccountGroup(eMyMoney::Account::Type::Equity);
358 
359   aSet.removeAccountType(eMyMoney::Account::Type::CertificateDep);
360   aSet.removeAccountType(eMyMoney::Account::Type::Investment);
361   aSet.removeAccountType(eMyMoney::Account::Type::Stock);
362   aSet.removeAccountType(eMyMoney::Account::Type::MoneyMarket);
363   if (category)
364     aSet.load(category->selector());
365 
366   // if an account is specified then remove it from the widget so that the user
367   // cannot create a transfer with from and to account being the same account
368   if (category && !d->m_account.id().isEmpty())
369     category->selector()->removeItem(d->m_account.id());
370 
371   //  also show memo text if isMultiSelection()
372   if (auto memoWidget = dynamic_cast<KTextEdit*>(d->m_editWidgets["memo"]))
373     memoWidget->setText(d->m_split.memo());
374   // need to know if it changed
375   d->m_memoText = d->m_split.memo();
376   d->m_memoChanged = false;
377 
378   if (!isMultiSelection()) {
379     if (auto dateWidget = dynamic_cast<KMyMoneyDateInput*>(d->m_editWidgets["postdate"])) {
380       if (d->m_transaction.postDate().isValid())
381         dateWidget->setDate(d->m_transaction.postDate());
382       else if (d->m_lastPostDate.isValid())
383         dateWidget->setDate(d->m_lastPostDate);
384       else
385         dateWidget->setDate(QDate::currentDate());
386     }
387 
388     if ((w = haveWidget("number")) != 0) {
389       if (auto lineEdit = dynamic_cast<KMyMoneyLineEdit*>(w))
390         lineEdit->loadText(d->m_split.number());
391       if (d->m_transaction.id().isEmpty()                              // new transaction
392           && dynamic_cast<KMyMoneyLineEdit*>(w)->text().isEmpty()   // no number filled in
393           && d->m_account.accountType() == eMyMoney::Account::Type::Checkings   // checkings account
394           && KMyMoneySettings::autoIncCheckNumber()           // and auto inc number turned on?
395           && action != eRegister::Action::Deposit              // only transfers or withdrawals
396           && d->m_paymentMethod == eMyMoney::Schedule::PaymentType::WriteChecque) {// only for WriteChecque
397         assignNextNumber();
398       }
399     }
400     if (auto statusWidget = dynamic_cast<KMyMoneyReconcileCombo*>(d->m_editWidgets["status"]))
401       statusWidget->setState(d->m_split.reconcileFlag());
402 
403     QString payeeId = d->m_split.payeeId();
404     if (payee && !payeeId.isEmpty())
405       payee->setSelectedItem(payeeId);
406 
407     QList<QString> t = d->m_split.tagIdList();
408     if (tag && !t.isEmpty())
409       for (auto i = 0; i < t.size(); ++i)
410         tag->addTagWidget(t[i]);
411 
412     d->m_splits.clear();
413     if (d->m_transaction.splitCount() < 2) {
414       if (category && category->completion()) {
415         category->completion()->setSelected(QString());
416       }
417     } else {
418       foreach (const auto split, d->m_transaction.splits()) {
419         if (split == d->m_split)
420           continue;
421         d->m_splits.append(split);
422       }
423     }
424     QString categoryId;
425     setupCategoryWidget(categoryId);
426 
427     if ((w = haveWidget("cashflow")) != 0) {
428       if (auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(w))
429         cashflow->setDirection(!d->m_split.value().isPositive() ? eRegister::CashFlowDirection::Payment : eRegister::CashFlowDirection::Deposit);  //  include isZero case
430     }
431 
432     if ((w = haveWidget("category-label")) != 0) {
433       if (auto categoryLabel = dynamic_cast<QLabel*>(w)) {
434         if (isTransfer(d->m_split.accountId(), categoryId)) {
435           if (d->m_split.value().isPositive())
436             categoryLabel->setText(i18n("Transfer from"));
437           else
438             categoryLabel->setText(i18n("Transfer to"));
439         }
440       }
441     }
442 
443     MyMoneyMoney value = d->m_split.shares();
444 
445     if (haveWidget("deposit")) {
446       auto depositWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["deposit"]);
447       auto paymentWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["payment"]);
448       if (depositWidget && paymentWidget) {
449         if (d->m_split.shares().isNegative()) {
450           depositWidget->setText(QString());
451           paymentWidget->setValue(value.abs());
452         } else {
453           depositWidget->setValue(value.abs());
454           paymentWidget->setText(QString());
455         }
456       }
457     }
458     if ((w = haveWidget("amount")) != 0) {
459       if (auto amountWidget = dynamic_cast<AmountEdit*>(w))
460         amountWidget->setValue(value.abs());
461     }
462 
463     slotUpdateCategory(categoryId);
464 
465     // try to preset for specific action if a new transaction is being started
466     if (d->m_transaction.id().isEmpty()) {
467       if ((w = haveWidget("category-label")) != 0) {
468         auto tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(haveWidget("tabbar"));
469         if (action == eRegister::Action::None) {
470           if (tabbar) {
471             action = static_cast<eRegister::Action>(tabbar->currentIndex());
472           }
473         }
474         if (action != eRegister::Action::None) {
475           if (auto categoryLabel = dynamic_cast<QLabel*>(w)) {
476             if (action == eRegister::Action::Transfer) {
477               if (d->m_split.value().isPositive())
478                 categoryLabel->setText(i18n("Transfer from"));
479               else
480                 categoryLabel->setText(i18n("Transfer to"));
481             }
482           }
483           if ((w = haveWidget("cashflow")) != 0) {
484             if (auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(w)) {
485               if (action == eRegister::Action::Deposit || (action == eRegister::Action::Transfer && d->m_split.value().isPositive()))
486                 cashflow->setDirection(eRegister::CashFlowDirection::Deposit);
487               else
488                 cashflow->setDirection(eRegister::CashFlowDirection::Payment);
489             }
490           }
491           if (tabbar) {
492             tabbar->setCurrentIndex((int)action);
493           }
494         }
495       }
496     } else {
497       if (auto tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(haveWidget("tabbar"))) {
498         if (!isTransfer(d->m_split.accountId(), categoryId))
499           tabbar->setCurrentIndex(d->m_split.value().isNegative() ? (int)eRegister::Action::Withdrawal : (int)eRegister::Action::Deposit);
500         else
501           tabbar->setCurrentIndex((int)eRegister::Action::Transfer);
502       }
503     }
504 
505   } else {  //  isMultiSelection()
506     if (auto postDateWidget = dynamic_cast<KMyMoneyDateInput*>(d->m_editWidgets["postdate"]))
507       postDateWidget->loadDate(QDate());
508     if (auto statusWidget = dynamic_cast<KMyMoneyReconcileCombo*>(d->m_editWidgets["status"]))
509       statusWidget->setState(eMyMoney::Split::State::Unknown);
510     if (haveWidget("deposit")) {
511       if (auto depositWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["deposit"])) {
512         depositWidget->setText(QString());
513         depositWidget->setAllowEmpty();
514       }
515       if (auto paymentWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["payment"])) {
516         paymentWidget->setText(QString());
517         paymentWidget->setAllowEmpty();
518       }
519     }
520     if ((w = haveWidget("amount")) != 0) {
521       if (auto amountWidget = dynamic_cast<AmountEdit*>(w)) {
522         amountWidget->setText(QString());
523         amountWidget->setAllowEmpty();
524       }
525     }
526 
527     slotUpdateAction((int)action);
528 
529     if ((w = haveWidget("tabbar")) != 0) {
530       w->setEnabled(false);
531     }
532 
533     if (category && category->completion())
534       category->completion()->setSelected(QString());
535   }
536 
537   // allow kick off VAT processing again
538   d->m_inUpdateVat = false;
539 }
540 
loadEditWidgets()541 void StdTransactionEditor::loadEditWidgets()
542 {
543   loadEditWidgets(eRegister::Action::None);
544 }
545 
firstWidget() const546 QWidget* StdTransactionEditor::firstWidget() const
547 {
548   Q_D(const StdTransactionEditor);
549   QWidget* w = nullptr;
550   if (d->m_initialAction != eRegister::Action::None) {
551     w = haveWidget("payee");
552   }
553   return w;
554 }
555 
slotReloadEditWidgets()556 void StdTransactionEditor::slotReloadEditWidgets()
557 {
558   Q_D(StdTransactionEditor);
559   // reload category widget
560   if (auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"])) {
561     QString categoryId = category->selectedItem();
562 
563     AccountSet aSet;
564     aSet.addAccountGroup(eMyMoney::Account::Type::Asset);
565     aSet.addAccountGroup(eMyMoney::Account::Type::Liability);
566     aSet.addAccountGroup(eMyMoney::Account::Type::Income);
567     aSet.addAccountGroup(eMyMoney::Account::Type::Expense);
568     if (KMyMoneySettings::expertMode())
569       aSet.addAccountGroup(eMyMoney::Account::Type::Equity);
570     aSet.load(category->selector());
571 
572     // if an account is specified then remove it from the widget so that the user
573     // cannot create a transfer with from and to account being the same account
574     if (!d->m_account.id().isEmpty())
575       category->selector()->removeItem(d->m_account.id());
576 
577     if (!categoryId.isEmpty())
578       category->setSelectedItem(categoryId);
579   }
580 
581 
582   // reload payee widget
583   if (auto payee = dynamic_cast<KMyMoneyPayeeCombo*>(d->m_editWidgets["payee"])) {
584     QString payeeId = payee->selectedItem();
585 
586     payee->loadPayees(MyMoneyFile::instance()->payeeList());
587 
588     if (!payeeId.isEmpty()) {
589       payee->setSelectedItem(payeeId);
590     }
591   }
592 
593   // reload tag widget
594   if (auto tag = dynamic_cast<KTagContainer*>(d->m_editWidgets["tag"])) {
595     QString tagId = tag->tagCombo()->selectedItem();
596 
597     tag->loadTags(MyMoneyFile::instance()->tagList());
598 
599     if (!tagId.isEmpty()) {
600       tag->RemoveAllTagWidgets();
601       tag->addTagWidget(tagId);
602     }
603   }
604 }
605 
slotUpdatePayee(const QString & payeeId)606 void StdTransactionEditor::slotUpdatePayee(const QString& payeeId)
607 {
608   // in case of an empty payee, there is nothing to do
609   if (payeeId.isEmpty())
610     return;
611 
612   Q_D(StdTransactionEditor);
613   // we have a new payee assigned to this transaction.
614   // in case there is no category assigned, no value entered and no
615   // memo available, we search for the last transaction of this payee
616   // in the account.
617   if (d->m_transaction.id().isEmpty()
618       && d->m_splits.count() == 0
619       && KMyMoneySettings::autoFillTransaction() != 0) {
620     // check if category is empty
621     if (auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"])) {
622       QStringList list;
623       category->selectedItems(list);
624       if (!list.isEmpty())
625         return;
626     }
627 
628     // check if memo is empty
629     auto memo = dynamic_cast<KTextEdit*>(d->m_editWidgets["memo"]);
630     if (memo && !memo->toPlainText().isEmpty())
631       return;
632 
633     // check if all value fields are empty
634     QStringList fields;
635     fields << "amount" << "payment" << "deposit";
636     QStringList::const_iterator it_f;
637     for (it_f = fields.constBegin(); it_f != fields.constEnd(); ++it_f) {
638       const auto amount = dynamic_cast<AmountEdit*>(haveWidget(*it_f));
639       if (amount && !amount->value().isZero())
640         return;
641     }
642 
643 #if 0
644     // Tony mentioned, that autofill does not work when he changed the date. Well,
645     // that certainly makes sense when you enter transactions in register mode as
646     // opposed to form mode, because the date field is located prior to the date
647     // field in the tab order of the widgets and the user might have already
648     // changed it.
649     //
650     // So I commented out the code that checks the date but left it in for reference.
651     // (ipwizard, 2008-04-07)
652 
653     // check if date has been altered by user
654     auto postDate = dynamic_cast<KMyMoneyDateInput*>(m_editWidgets["postdate"]);
655     if (postDate && (m_lastPostDate.isValid() && (postDate->date() != m_lastPostDate))
656         || (!m_lastPostDate.isValid() && (postDate->date() != QDate::currentDate())))
657       return;
658 #endif
659 
660     // if we got here, we have to autofill
661     autoFill(payeeId);
662   }
663 
664   // If payee has associated default account (category), set that now.
665   const MyMoneyPayee& payeeObj = MyMoneyFile::instance()->payee(payeeId);
666   if (!payeeObj.defaultAccountId().isEmpty()) {
667     if (auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"]))
668       category->slotItemSelected(payeeObj.defaultAccountId());
669   }
670 }
671 
shares(const MyMoneyTransaction & t) const672 MyMoneyMoney StdTransactionEditor::shares(const MyMoneyTransaction& t) const
673 {
674   Q_D(const StdTransactionEditor);
675   MyMoneyMoney result;
676   foreach (const auto split, t.splits()) {
677     if (split.accountId() == d->m_account.id()) {
678       result += split.shares();
679     }
680   }
681   return result;
682 }
683 
684 struct uniqTransaction {
685   const MyMoneyTransaction* t;
686   int   cnt;
687 };
688 
autoFill(const QString & payeeId)689 void StdTransactionEditor::autoFill(const QString& payeeId)
690 {
691   Q_D(StdTransactionEditor);
692   QList<QPair<MyMoneyTransaction, MyMoneySplit> >  list;
693   MyMoneyTransactionFilter filter(d->m_account.id());
694   filter.addPayee(payeeId);
695   MyMoneyFile::instance()->transactionList(list, filter);
696   if (!list.empty()) {
697     // ok, we found at least one previous transaction. now we clear out
698     // what we have collected so far and add those splits from
699     // the previous transaction.
700     QList<QPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator  it_t;
701     QMap<QString, struct uniqTransaction> uniqList;
702 
703     // collect the transactions and see if we have any duplicates
704     for (it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) {
705       QString key = (*it_t).first.accountSignature();
706       int cnt = 0;
707       QMap<QString, struct uniqTransaction>::iterator it_u;
708       do {
709         QString ukey = QString("%1-%2").arg(key).arg(cnt);
710         it_u = uniqList.find(ukey);
711         if (it_u == uniqList.end()) {
712           uniqList[ukey].t = &((*it_t).first);
713           uniqList[ukey].cnt = 1;
714         } else if (KMyMoneySettings::autoFillTransaction() == 1) {
715           // we already have a transaction with this signature. we must
716           // now check, if we should really treat it as a duplicate according
717           // to the value comparison delta.
718           MyMoneyMoney s1 = shares(*((*it_u).t));
719           MyMoneyMoney s2 = shares((*it_t).first);
720           if (s2.abs() > s1.abs()) {
721             MyMoneyMoney t(s1);
722             s1 = s2;
723             s2 = t;
724           }
725           MyMoneyMoney diff;
726           if (s2.isZero())
727             diff = s1.abs();
728           else
729             diff = ((s1 - s2) / s2).convert(10000);
730           if (diff.isPositive() && diff <= MyMoneyMoney(KMyMoneySettings::autoFillDifference(), 100)) {
731             uniqList[ukey].t = &((*it_t).first);
732             break;    // end while loop
733           }
734         } else if (KMyMoneySettings::autoFillTransaction() == 2) {
735           uniqList[ukey].t = &((*it_t).first);
736           (*it_u).cnt++;
737           break;      // end while loop
738         }
739         ++cnt;
740       } while (it_u != uniqList.end());
741 
742     }
743 
744     MyMoneyTransaction t;
745     if (KMyMoneySettings::autoFillTransaction() != 2) {
746 #if 0
747       // I removed this code to allow cancellation of an autofill if
748       // it does not match even if there is only a single matching
749       // transaction for the payee in question. In case, we want to revert
750       // to the old behavior, don't forget to uncomment the closing
751       // brace further down in the code as well. (ipwizard 2009-01-16)
752       if (uniqList.count() == 1) {
753         t = list.last().first;
754       } else {
755 #endif
756         QPointer<KSelectTransactionsDlg> dlg = new KSelectTransactionsDlg(d->m_account, d->m_regForm);
757         dlg->setWindowTitle(i18n("Select autofill transaction"));
758 
759         QMap<QString, struct uniqTransaction>::const_iterator it_u;
760         for (it_u = uniqList.constBegin(); it_u != uniqList.constEnd(); ++it_u) {
761           dlg->addTransaction(*(*it_u).t);
762         }
763 
764         auto tRegister = dlg->getRegister();
765         // setup sort order
766         tRegister->setSortOrder("1,-9,-4");
767         // sort the transactions according to the sort setting
768         tRegister->sortItems();
769 
770         // and select the last item
771         if (tRegister->lastItem())
772           tRegister->selectItem(tRegister->lastItem());
773 
774         if (dlg->exec() == QDialog::Accepted) {
775           t = dlg->transaction();
776         }
777 #if 0
778       }
779 #endif
780     } else {
781       int maxCnt = 0;
782       QMap<QString, struct uniqTransaction>::const_iterator it_u;
783       for (it_u = uniqList.constBegin(); it_u != uniqList.constEnd(); ++it_u) {
784         if ((*it_u).cnt > maxCnt) {
785           t = *(*it_u).t;
786           maxCnt = (*it_u).cnt;
787         }
788       }
789     }
790 
791     if (t != MyMoneyTransaction()) {
792       d->m_transaction.removeSplits();
793       d->m_split = MyMoneySplit();
794       MyMoneySplit otherSplit;
795       foreach (const auto split, t.splits()) {
796         MyMoneySplit s(split);
797         s.setReconcileFlag(eMyMoney::Split::State::NotReconciled);
798         s.setReconcileDate(QDate());
799         s.clearId();
800         s.setBankID(QString());
801         // older versions of KMyMoney used to set the action
802         // we don't need this anymore
803         if (s.action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization)
804             && s.action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Interest))  {
805           s.setAction(QString());
806         }
807 
808         // FIXME update check number. The old comment contained
809         //
810         // <quote>
811         // If a check number is already specified by the user it is
812         // used. If the input field is empty and the previous transaction
813         // contains a checknumber, the next usable check number will be assigned
814         // to the transaction.
815         // </quote>
816 
817         auto editNr = dynamic_cast<KMyMoneyLineEdit*>(haveWidget("number"));
818         if (editNr && !editNr->text().isEmpty()) {
819           s.setNumber(editNr->text());
820         } else if (!s.number().isEmpty()) {
821           s.setNumber(KMyMoneyUtils::nextFreeCheckNumber(d->m_account));
822         }
823 
824         // if the memos should not be used with autofill or
825         // if the transaction has exactly two splits, remove
826         // the memo text of the split that does not reference
827         // the current account. This allows the user to change
828         // the autofilled memo text which will then also be used
829         // in this split. See createTransaction() for this logic.
830         if ((s.accountId() != d->m_account.id() && t.splitCount() == 2) || !KMyMoneySettings::autoFillUseMemos())
831           s.setMemo(QString());
832 
833         d->m_transaction.addSplit(s);
834         if (s.accountId() == d->m_account.id() && d->m_split == MyMoneySplit()) {
835           d->m_split = s;
836         } else {
837           otherSplit = s;
838         }
839       }
840 
841       // make sure to extract the right action
842       eRegister::Action action;
843       action = d->m_split.shares().isNegative() ? eRegister::Action::Withdrawal : eRegister::Action::Deposit;
844 
845       if (d->m_transaction.splitCount() == 2) {
846         auto acc = MyMoneyFile::instance()->account(otherSplit.accountId());
847         if (acc.isAssetLiability())
848           action = eRegister::Action::Transfer;
849       }
850 
851       // now setup the widgets with the new data but keep the date
852       if (auto postdateWidget = dynamic_cast<KMyMoneyDateInput*>(d->m_editWidgets["postdate"])) {
853         auto date = postdateWidget->date();
854         loadEditWidgets(action);
855         postdateWidget->setDate(date);
856       }
857     }
858   }
859 
860   // focus jumps into the category field
861   QWidget* w;
862   if ((w = haveWidget("payee")) != 0) {
863     w->setFocus();
864   }
865 }
866 
slotUpdateAction(int action)867 void StdTransactionEditor::slotUpdateAction(int action)
868 {
869   Q_D(StdTransactionEditor);
870   auto tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(haveWidget("tabbar"));
871   if (tabbar) {
872     auto categoryLabel = dynamic_cast<QLabel*>(haveWidget("category-label"));
873     auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(d->m_editWidgets["cashflow"]);
874     if (!categoryLabel || !cashflow)
875       return;
876     switch (action) {
877       case (int)eRegister::Action::Deposit:
878         categoryLabel->setText(i18n("Category"));
879         cashflow->setDirection(eRegister::CashFlowDirection::Deposit);
880         break;
881       case (int)eRegister::Action::Transfer:
882         if (d->m_split.shares().isNegative()) {
883           cashflow->setDirection(eRegister::CashFlowDirection::Payment);
884           categoryLabel->setText(i18n("Transfer to"));
885         } else {
886           cashflow->setDirection(eRegister::CashFlowDirection::Deposit);
887           categoryLabel->setText(i18n("Transfer from"));
888         }
889         tabbar->setCurrentIndex((int)eRegister::Action::Transfer);
890         slotUpdateCashFlow(cashflow->direction());
891         break;
892       case (int)eRegister::Action::Withdrawal:
893         categoryLabel->setText(i18n("Category"));
894         cashflow->setDirection(eRegister::CashFlowDirection::Payment);
895         break;
896     }
897     resizeForm();
898   }
899 }
900 
slotUpdateCashFlow(eRegister::CashFlowDirection dir)901 void StdTransactionEditor::slotUpdateCashFlow(eRegister::CashFlowDirection dir)
902 {
903   auto categoryLabel = dynamic_cast<QLabel*>(haveWidget("category-label"));
904   if (auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(haveWidget("cashflow")))
905     cashflow->setDirection(dir);
906   // qDebug("Update cashflow to %d", dir);
907   if (categoryLabel) {
908     auto tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(haveWidget("tabbar"));
909     if (!tabbar) return;  //  no transaction form
910     if (categoryLabel->text() != i18n("Category")) {
911       tabbar->setCurrentIndex((int)eRegister::Action::Transfer);
912       if (dir == eRegister::CashFlowDirection::Deposit) {
913         categoryLabel->setText(i18n("Transfer from"));
914       } else {
915         categoryLabel->setText(i18n("Transfer to"));
916       }
917       resizeForm();
918     } else {
919       if (dir == eRegister::CashFlowDirection::Deposit)
920         tabbar->setCurrentIndex((int)eRegister::Action::Deposit);
921       else
922         tabbar->setCurrentIndex((int)eRegister::Action::Withdrawal);
923     }
924   }
925 }
926 
slotUpdateCategory(const QString & id)927 void StdTransactionEditor::slotUpdateCategory(const QString& id)
928 {
929   Q_D(StdTransactionEditor);
930   auto categoryLabel = dynamic_cast<QLabel*>(haveWidget("category-label"));
931   // qDebug("Update category to %s", qPrintable(id));
932 
933   if (categoryLabel) {
934     auto tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(haveWidget("tabbar"));
935     auto amount = dynamic_cast<AmountEdit*>(d->m_editWidgets["amount"]);
936     auto val = amount ? amount->value() : MyMoneyMoney();
937 
938     if (categoryLabel->text() == i18n("Transfer from")) {
939       val = -val;
940     } else {
941       val = val.abs();
942     }
943 
944     if (tabbar) {
945       tabbar->setTabEnabled((int)eRegister::Action::Transfer, true);
946       tabbar->setTabEnabled((int)eRegister::Action::Deposit, true);
947       tabbar->setTabEnabled((int)eRegister::Action::Withdrawal, true);
948     }
949 
950     bool disableTransferTab = false;
951     if (!id.isEmpty()) {
952       auto acc = MyMoneyFile::instance()->account(id);
953       if (acc.isAssetLiability()
954           || acc.accountGroup() == eMyMoney::Account::Type::Equity) {
955         if (tabbar) {
956           tabbar->setCurrentIndex((int)eRegister::Action::Transfer);
957           tabbar->setTabEnabled((int)eRegister::Action::Deposit, false);
958           tabbar->setTabEnabled((int)eRegister::Action::Withdrawal, false);
959         }
960         auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(d->m_editWidgets["cashflow"]);
961         if (val.isZero()) {
962           if (cashflow && (cashflow->direction() == eRegister::CashFlowDirection::Deposit)) {
963             categoryLabel->setText(i18n("Transfer from"));
964           } else {
965             categoryLabel->setText(i18n("Transfer to"));
966           }
967         } else if (val.isNegative()) {
968           categoryLabel->setText(i18n("Transfer from"));
969           if (cashflow)
970             cashflow->setDirection(eRegister::CashFlowDirection::Deposit);
971         } else
972           categoryLabel->setText(i18n("Transfer to"));
973       } else {
974         disableTransferTab = true;
975         categoryLabel->setText(i18n("Category"));
976       }
977       updateAmount(val);
978     } else {  //id.isEmpty()
979       if (auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"]))
980         disableTransferTab = !category->currentText().isEmpty();
981       categoryLabel->setText(i18n("Category"));
982     }
983     if (tabbar) {
984       if (disableTransferTab) {
985         // set the proper tab before disabling the currently active tab
986         if (tabbar->currentIndex() == (int)eRegister::Action::Transfer) {
987           tabbar->setCurrentIndex(val.isPositive() ? (int)eRegister::Action::Withdrawal : (int)eRegister::Action::Deposit);
988         }
989         tabbar->setTabEnabled((int)eRegister::Action::Transfer, false);
990       }
991       tabbar->update();
992     }
993 
994     resizeForm();
995   }
996   updateVAT(false);
997 }
998 
slotUpdatePayment(const QString & txt)999 void StdTransactionEditor::slotUpdatePayment(const QString& txt)
1000 {
1001   Q_D(StdTransactionEditor);
1002   MyMoneyMoney val(txt);
1003 
1004   auto depositWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["deposit"]);
1005   auto paymentWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["payment"]);
1006   if (!depositWidget || !paymentWidget)
1007     return;
1008 
1009   if (val.isNegative()) {
1010     depositWidget->setValue(val.abs());
1011     paymentWidget->setText(QString());
1012   } else {
1013     depositWidget->setText(QString());
1014   }
1015   updateVAT();
1016 }
1017 
slotUpdateDeposit(const QString & txt)1018 void StdTransactionEditor::slotUpdateDeposit(const QString& txt)
1019 {
1020   Q_D(StdTransactionEditor);
1021   MyMoneyMoney val(txt);
1022 
1023   auto depositWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["deposit"]);
1024   auto paymentWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["payment"]);
1025   if (!depositWidget || !paymentWidget)
1026     return;
1027 
1028   if (val.isNegative()) {
1029     paymentWidget->setValue(val.abs());
1030     depositWidget->setText(QString());
1031   } else {
1032     paymentWidget->setText(QString());
1033   }
1034   updateVAT();
1035 }
1036 
slotUpdateAmount(const QString & txt)1037 void StdTransactionEditor::slotUpdateAmount(const QString& txt)
1038 {
1039   // qDebug("Update amount to %s", qPrintable(txt));
1040   MyMoneyMoney val(txt);
1041   updateAmount(val);
1042   updateVAT(true);
1043 }
1044 
updateAmount(const MyMoneyMoney & val)1045 void StdTransactionEditor::updateAmount(const MyMoneyMoney& val)
1046 {
1047   // we don't do anything if we have multiple transactions selected
1048   if (isMultiSelection())
1049     return;
1050 
1051   Q_D(StdTransactionEditor);
1052   auto categoryLabel = dynamic_cast<QLabel*>(haveWidget("category-label"));
1053   if (categoryLabel) {
1054     if (auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(d->m_editWidgets["cashflow"])) {
1055       if (!val.isPositive())  {  //   fixes BUG321317
1056         if (categoryLabel->text() != i18n("Category")) {
1057           if (cashflow->direction() == eRegister::CashFlowDirection::Payment) {
1058             categoryLabel->setText(i18n("Transfer to"));
1059           }
1060         } else {
1061           slotUpdateCashFlow(cashflow->direction());
1062         }
1063         if (auto amountWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["amount"]))
1064           amountWidget->setValue(val.abs());
1065       } else {
1066         if (categoryLabel->text() != i18n("Category")) {
1067           if (cashflow->direction() == eRegister::CashFlowDirection::Payment) {
1068             categoryLabel->setText(i18n("Transfer to"));
1069           } else {
1070             categoryLabel->setText(i18n("Transfer from"));
1071             cashflow->setDirection(eRegister::CashFlowDirection::Deposit);  //  editing with +ve shows 'from' not 'pay to'
1072           }
1073         }
1074         if (auto amountWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["amount"]))
1075           amountWidget->setValue(val.abs());
1076       }
1077     }
1078   }
1079 }
1080 
updateVAT(bool amountChanged)1081 void StdTransactionEditor::updateVAT(bool amountChanged)
1082 {
1083   Q_D(StdTransactionEditor);
1084   // make sure that we don't do this recursively
1085   if (d->m_inUpdateVat)
1086     return;
1087 
1088   // we don't do anything if we have multiple transactions selected
1089   if (isMultiSelection())
1090     return;
1091 
1092   // if auto vat assignment for this account is turned off
1093   // we don't care about taxes
1094   if (d->m_account.value("NoVat") == "Yes")
1095     return;
1096 
1097   // more splits than category and tax are not supported
1098   if (d->m_splits.count() > 2)
1099     return;
1100 
1101   // in order to do anything, we need an amount
1102   MyMoneyMoney amount, newAmount;
1103   bool amountOk;
1104   amount = amountFromWidget(&amountOk);
1105   if (!amountOk)
1106     return;
1107 
1108   // If the transaction has a tax and a category split, remove the tax split
1109   if (d->m_splits.count() == 2) {
1110     newAmount = removeVatSplit();
1111     if (d->m_splits.count() == 2) // not removed?
1112       return;
1113 
1114   } else if (auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"])) {
1115     // otherwise, we need a category
1116     if (category->selectedItem().isEmpty())
1117       return;
1118 
1119     // if no VAT account is associated with this category/account, then we bail out
1120     MyMoneyAccount cat = MyMoneyFile::instance()->account(category->selectedItem());
1121     if (cat.value("VatAccount").isEmpty())
1122       return;
1123 
1124     newAmount = amount;
1125   }
1126 
1127   // seems we have everything we need
1128   if (amountChanged)
1129     newAmount = amount;
1130 
1131   MyMoneyTransaction transaction;
1132   if (createTransaction(transaction, d->m_transaction, d->m_split)) {
1133     if (addVatSplit(transaction, newAmount)) {
1134       d->m_transaction = transaction;
1135       if (!d->m_transaction.splits().isEmpty())
1136         d->m_split = d->m_transaction.splits().front();
1137 
1138       loadEditWidgets();
1139 
1140       // if we made this a split transaction, then move the
1141       // focus to the memo field
1142       if (qApp->focusWidget() == haveWidget("category")) {
1143         QWidget* w = haveWidget("memo");
1144         if (w)
1145           w->setFocus();
1146       }
1147     }
1148   }
1149 }
1150 
addVatSplit(MyMoneyTransaction & tr,const MyMoneyMoney & amount)1151 bool StdTransactionEditor::addVatSplit(MyMoneyTransaction& tr, const MyMoneyMoney& amount)
1152 {
1153   if (tr.splitCount() != 2)
1154     return false;
1155 
1156   Q_D(StdTransactionEditor);
1157   auto file = MyMoneyFile::instance();
1158   // extract the category split from the transaction
1159   MyMoneyAccount category = file->account(tr.splitByAccount(d->m_account.id(), false).accountId());
1160   return file->addVATSplit(tr, d->m_account, category, amount);
1161 }
1162 
removeVatSplit()1163 MyMoneyMoney StdTransactionEditor::removeVatSplit()
1164 {
1165   Q_D(StdTransactionEditor);
1166   // we only deal with splits that have three splits
1167   if (d->m_splits.count() != 2)
1168     return amountFromWidget();
1169 
1170   MyMoneySplit c; // category split
1171   MyMoneySplit t; // tax split
1172 
1173   auto netValue = false;
1174   foreach (const auto split , d->m_splits) {
1175     auto acc = MyMoneyFile::instance()->account(split.accountId());
1176     if (!acc.value("VatAccount").isEmpty()) {
1177       netValue = (acc.value("VatAmount").toLower() == "net");
1178       c = split;
1179     } else if (!acc.value("VatRate").isEmpty()) {
1180       t = split;
1181     }
1182   }
1183 
1184   // bail out if not all splits are setup
1185   if (c.id().isEmpty() || t.id().isEmpty())
1186     return amountFromWidget();
1187 
1188   MyMoneyMoney amount;
1189   // reduce the splits
1190   if (netValue) {
1191     amount = -c.shares();
1192   } else {
1193     amount = -(c.shares() + t.shares());
1194   }
1195 
1196   // remove tax split from the list, ...
1197   d->m_splits.clear();
1198   d->m_splits.append(c);
1199 
1200   // ... make sure that the widget is updated ...
1201   // block the signals to avoid popping up the split editor dialog
1202   // for nothing
1203   d->m_editWidgets["category"]->blockSignals(true);
1204   QString id;
1205   setupCategoryWidget(id);
1206   d->m_editWidgets["category"]->blockSignals(false);
1207 
1208   // ... and return the updated amount
1209   return amount;
1210 }
1211 
isComplete(QString & reason) const1212 bool StdTransactionEditor::isComplete(QString& reason) const
1213 {
1214   Q_D(const StdTransactionEditor);
1215   reason.clear();
1216   QMap<QString, QWidget*>::const_iterator it_w;
1217 
1218   auto postDate = dynamic_cast<KMyMoneyDateInput*>(d->m_editWidgets["postdate"]);
1219   if (postDate) {
1220     QDate accountOpeningDate = d->m_account.openingDate();
1221     for (QList<MyMoneySplit>::const_iterator it_s = d->m_splits.constBegin(); it_s != d->m_splits.constEnd(); ++it_s) {
1222       const MyMoneyAccount& acc = MyMoneyFile::instance()->account((*it_s).accountId());
1223       // compute the newest opening date of all accounts involved in the transaction
1224       if (acc.openingDate() > accountOpeningDate)
1225         accountOpeningDate = acc.openingDate();
1226     }
1227     // check the selected category in case m_splits hasn't been updated yet
1228     auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"]);
1229     if (category && !category->selectedItem().isEmpty()) {
1230       MyMoneyAccount cat = MyMoneyFile::instance()->account(category->selectedItem());
1231       if (cat.openingDate() > accountOpeningDate)
1232         accountOpeningDate = cat.openingDate();
1233     }
1234 
1235     if (postDate->date().isValid() && (postDate->date() < accountOpeningDate)) {
1236       postDate->markAsBadDate(true, KMyMoneySettings::schemeColor(SchemeColor::Negative));
1237       reason = i18n("Cannot enter transaction with postdate prior to account's opening date.");
1238       postDate->setToolTip(reason);
1239       return false;
1240     }
1241     postDate->markAsBadDate();
1242     postDate->setToolTip(QString());
1243   }
1244 
1245   for (it_w = d->m_editWidgets.begin(); it_w != d->m_editWidgets.end(); ++it_w) {
1246     auto payee = dynamic_cast<KMyMoneyPayeeCombo*>(*it_w);
1247     auto tagContainer = dynamic_cast<KTagContainer*>(*it_w);
1248     auto category = dynamic_cast<KMyMoneyCategory*>(*it_w);
1249     auto amount = dynamic_cast<AmountEdit*>(*it_w);
1250     auto reconcile = dynamic_cast<KMyMoneyReconcileCombo*>(*it_w);
1251     auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(*it_w);
1252     auto memo = dynamic_cast<KTextEdit*>(*it_w);
1253 
1254     if (payee && !(payee->currentText().isEmpty()))
1255       break;
1256 
1257     if (category && !category->lineEdit()->text().isEmpty())
1258       break;
1259 
1260     if (amount && !(amount->value().isZero()))
1261       break;
1262 
1263     // the following widgets are only checked if we are editing multiple transactions
1264     if (isMultiSelection()) {
1265       if (auto tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(haveWidget("tabbar")))
1266         tabbar->setEnabled(true);
1267 
1268       if (reconcile && reconcile->state() != eMyMoney::Split::State::Unknown)
1269         break;
1270 
1271       if (cashflow && cashflow->direction() != eRegister::CashFlowDirection::Unknown)
1272         break;
1273 
1274       if (postDate && postDate->date().isValid() && (postDate->date() >= d->m_account.openingDate()))
1275         break;
1276 
1277       if (memo && d->m_memoChanged)
1278         break;
1279 
1280       if (tagContainer && !(tagContainer->selectedTags().isEmpty()))  //  Tag is optional field
1281         break;
1282     }
1283   }
1284   return it_w != d->m_editWidgets.end();
1285 }
1286 
slotCreateCategory(const QString & name,QString & id)1287 void StdTransactionEditor::slotCreateCategory(const QString& name, QString& id)
1288 {
1289   Q_D(StdTransactionEditor);
1290   MyMoneyAccount acc, parent;
1291   acc.setName(name);
1292 
1293   auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(haveWidget("cashflow"));
1294   if (cashflow) {
1295     // form based input
1296     if (cashflow->direction() == eRegister::CashFlowDirection::Deposit)
1297       parent = MyMoneyFile::instance()->income();
1298     else
1299       parent = MyMoneyFile::instance()->expense();
1300 
1301   } else if (haveWidget("deposit")) {
1302     // register based input
1303     if (auto deposit = dynamic_cast<AmountEdit*>(d->m_editWidgets["deposit"])) {
1304       if (deposit->value().isPositive())
1305         parent = MyMoneyFile::instance()->income();
1306       else
1307         parent = MyMoneyFile::instance()->expense();
1308     }
1309 
1310   } else
1311     parent = MyMoneyFile::instance()->expense();
1312 
1313   // TODO extract possible first part of a hierarchy and check if it is one
1314   // of our top categories. If so, remove it and select the parent
1315   // according to this information.
1316 
1317   slotNewCategory(acc, parent);
1318 
1319   // return id
1320   id = acc.id();
1321 }
1322 
slotEditSplits()1323 int StdTransactionEditor::slotEditSplits()
1324 {
1325   Q_D(StdTransactionEditor);
1326   int rc = QDialog::Rejected;
1327 
1328   if (!d->m_openEditSplits) {
1329     // only get in here in a single instance
1330     d->m_openEditSplits = true;
1331 
1332     // force focus change to update all data
1333     auto categoryWidget = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"]);
1334     QWidget* w = categoryWidget ? categoryWidget->splitButton() : nullptr;
1335     if (w)
1336       w->setFocus();
1337 
1338     auto amount = dynamic_cast<AmountEdit*>(haveWidget("amount"));
1339     auto deposit = dynamic_cast<AmountEdit*>(haveWidget("deposit"));
1340     auto payment = dynamic_cast<AmountEdit*>(haveWidget("payment"));
1341     KMyMoneyCashFlowCombo* cashflow = 0;
1342     eRegister::CashFlowDirection dir = eRegister::CashFlowDirection::Unknown;
1343     bool isValidAmount = false;
1344 
1345     if (amount) {
1346       isValidAmount = amount->text().length() != 0;
1347       if ((cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(haveWidget("cashflow"))))
1348         dir = cashflow->direction();
1349 
1350     } else {
1351       if (deposit) {
1352         if (deposit->text().length() != 0) {
1353           isValidAmount = true;
1354           dir = eRegister::CashFlowDirection::Deposit;
1355         }
1356       }
1357       if (payment) {
1358         if (payment->text().length() != 0) {
1359           isValidAmount = true;
1360           dir = eRegister::CashFlowDirection::Payment;
1361         }
1362       }
1363       if (!deposit || !payment) {
1364         qDebug("Internal error: deposit(%p) & payment(%p) widgets not found but required", deposit, payment);
1365         return rc;
1366       }
1367     }
1368 
1369     if (dir == eRegister::CashFlowDirection::Unknown)
1370       dir = eRegister::CashFlowDirection::Payment;
1371 
1372     MyMoneyTransaction transaction;
1373     if (createTransaction(transaction, d->m_transaction, d->m_split)) {
1374       MyMoneyMoney value;
1375 
1376       QPointer<KSplitTransactionDlg> dlg =
1377         new KSplitTransactionDlg(transaction,
1378                                  transaction.splits().isEmpty() ? MyMoneySplit() : transaction.splits().front(),
1379                                  d->m_account,
1380                                  isValidAmount,
1381                                  dir == eRegister::CashFlowDirection::Deposit,
1382                                  MyMoneyMoney(),
1383                                  d->m_priceInfo,
1384                                  d->m_regForm);
1385       connect(dlg.data(), &KSplitTransactionDlg::objectCreation, this, &StdTransactionEditor::objectCreation);
1386       connect(dlg.data(), &KSplitTransactionDlg::createCategory, this, &StdTransactionEditor::slotNewCategory);
1387 
1388       if ((rc = dlg->exec()) == QDialog::Accepted) {
1389         d->m_transaction = dlg->transaction();
1390         if (!d->m_transaction.splits().isEmpty()) {
1391           d->m_split = d->m_transaction.splits().front();
1392           // if we have only two splits left, we copy the memo
1393           // of the second (data from the split editor) to the
1394           // first (data used in the transaction editor)
1395           if (d->m_transaction.splitCount() == 2) {
1396             d->m_split.setMemo(d->m_transaction.splits().last().memo());
1397             d->m_transaction.modifySplit(d->m_split);
1398           }
1399         }
1400         loadEditWidgets();
1401       }
1402 
1403       delete dlg;
1404     }
1405 
1406     // focus jumps into the tag field
1407     if ((w = haveWidget("tag")) != 0) {
1408       w->setFocus();
1409     }
1410 
1411     d->m_openEditSplits = false;
1412   }
1413 
1414   return rc;
1415 }
1416 
checkPayeeInSplit(MyMoneySplit & s,const QString & payeeId)1417 void StdTransactionEditor::checkPayeeInSplit(MyMoneySplit& s, const QString& payeeId)
1418 {
1419   if (s.accountId().isEmpty())
1420     return;
1421 
1422   auto acc = MyMoneyFile::instance()->account(s.accountId());
1423   if (acc.isIncomeExpense()) {
1424     s.setPayeeId(payeeId);
1425   } else {
1426     if (s.payeeId().isEmpty())
1427       s.setPayeeId(payeeId);
1428   }
1429 }
1430 
amountFromWidget(bool * update) const1431 MyMoneyMoney StdTransactionEditor::amountFromWidget(bool* update) const
1432 {
1433   Q_D(const StdTransactionEditor);
1434   bool updateValue = false;
1435   MyMoneyMoney value;
1436 
1437   auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(haveWidget("cashflow"));
1438   if (cashflow) {
1439     // form based input
1440     if (auto amount = dynamic_cast<AmountEdit*>(d->m_editWidgets["amount"])) {
1441       // if both fields do not contain changes -> no need to update
1442       if (cashflow->direction() != eRegister::CashFlowDirection::Unknown
1443           && !amount->text().isEmpty())
1444         updateValue = true;
1445       value = amount->value();
1446       if (cashflow->direction() == eRegister::CashFlowDirection::Payment)
1447         value = -value;
1448     }
1449 
1450   } else if (haveWidget("deposit")) {
1451     // register based input
1452     auto deposit = dynamic_cast<AmountEdit*>(d->m_editWidgets["deposit"]);
1453     auto payment = dynamic_cast<AmountEdit*>(d->m_editWidgets["payment"]);
1454     if (deposit && payment) {
1455       // if both fields do not contain text -> no need to update
1456       if (!(deposit->text().isEmpty() && payment->text().isEmpty()))
1457         updateValue = true;
1458 
1459       if (deposit->value().isPositive())
1460         value = deposit->value();
1461       else
1462         value = -(payment->value());
1463     }
1464   }
1465 
1466   if (update)
1467     *update = updateValue;
1468 
1469   // determine the max fraction for this account and
1470   // adjust the value accordingly
1471   return value.convert(d->m_account.fraction());
1472 }
1473 
createTransaction(MyMoneyTransaction & t,const MyMoneyTransaction & torig,const MyMoneySplit & sorig,bool skipPriceDialog)1474 bool StdTransactionEditor::createTransaction(MyMoneyTransaction& t, const MyMoneyTransaction& torig, const MyMoneySplit& sorig, bool skipPriceDialog)
1475 {
1476   Q_D(StdTransactionEditor);
1477   // extract price info from original transaction
1478   d->m_priceInfo.clear();
1479   if (!torig.id().isEmpty()) {
1480     foreach (const auto split, torig.splits()) {
1481       if (split.id() != sorig.id()) {
1482         MyMoneyAccount cat = MyMoneyFile::instance()->account(split.accountId());
1483         if (cat.currencyId() != d->m_account.currencyId()) {
1484           if (!split.shares().isZero() && !split.value().isZero()) {
1485             d->m_priceInfo[cat.currencyId()] = (split.shares() / split.value()).reduce();
1486           }
1487         }
1488       }
1489     }
1490   }
1491 
1492   t = torig;
1493 
1494   t.removeSplits();
1495   t.setCommodity(d->m_account.currencyId());
1496 
1497   auto postDate = dynamic_cast<KMyMoneyDateInput*>(d->m_editWidgets["postdate"]);
1498   if (postDate && postDate->date().isValid()) {
1499     t.setPostDate(postDate->date());
1500   }
1501 
1502   // we start with the previous values, make sure we can add them later on
1503   MyMoneySplit s0 = sorig;
1504   s0.clearId();
1505 
1506   // make sure we reference this account here
1507   s0.setAccountId(d->m_account.id());
1508 
1509   // memo and number field are special: if we have multiple transactions selected
1510   // and the edit field is empty, we treat it as "not modified".
1511   // FIXME a better approach would be to have a 'dirty' flag with the widgets
1512   //       which identifies if the originally loaded value has been modified
1513   //       by the user
1514   auto memo = dynamic_cast<KTextEdit*>(d->m_editWidgets["memo"]);
1515   if (memo) {
1516     if (!isMultiSelection() || d->m_memoChanged)
1517       s0.setMemo(memo->toPlainText());
1518   }
1519 
1520   if (auto number = dynamic_cast<KMyMoneyLineEdit*>(haveWidget("number"))) {
1521     if (!isMultiSelection() || !number->text().isEmpty())
1522       s0.setNumber(number->text());
1523   }
1524 
1525   auto payee = dynamic_cast<KMyMoneyPayeeCombo*>(d->m_editWidgets["payee"]);
1526   QString payeeId;
1527   if (payee && (!isMultiSelection() || !payee->currentText().isEmpty())) {
1528     payeeId = payee->selectedItem();
1529     s0.setPayeeId(payeeId);
1530   }
1531 
1532   //KMyMoneyTagCombo* tag = dynamic_cast<KMyMoneyTagCombo*>(m_editWidgets["tag"]);
1533   auto tag = dynamic_cast<KTagContainer*>(d->m_editWidgets["tag"]);
1534   if (tag && (!isMultiSelection() || !tag->selectedTags().isEmpty())) {
1535     s0.setTagIdList(tag->selectedTags());
1536   }
1537 
1538   bool updateValue;
1539   MyMoneyMoney value = amountFromWidget(&updateValue);
1540 
1541   if (updateValue) {
1542     // for this account, the shares and value is the same
1543     s0.setValue(value);
1544     s0.setShares(value);
1545   } else {
1546     value = s0.value();
1547   }
1548 
1549   // if we mark the split reconciled here, we'll use today's date if no reconciliation date is given
1550   auto status = dynamic_cast<KMyMoneyReconcileCombo*>(d->m_editWidgets["status"]);
1551   if (status && status->state() != eMyMoney::Split::State::Unknown)
1552     s0.setReconcileFlag(status->state());
1553 
1554   if (s0.reconcileFlag() == eMyMoney::Split::State::Reconciled && !s0.reconcileDate().isValid())
1555     s0.setReconcileDate(QDate::currentDate());
1556 
1557   checkPayeeInSplit(s0, payeeId);
1558 
1559   // add the split to the transaction
1560   t.addSplit(s0);
1561 
1562   // if we have no other split we create it
1563   // if we have none or only one other split, we reconstruct it here
1564   // if we have more than one other split, we take them as they are
1565   // make sure to perform all those changes on a local copy
1566   QList<MyMoneySplit> splits = d->m_splits;
1567 
1568   MyMoneySplit s1;
1569   if (splits.isEmpty()) {
1570     s1.setMemo(s0.memo());
1571     splits.append(s1);
1572 
1573     // make sure we will fill the value and share fields later on
1574     updateValue = true;
1575   }
1576 
1577   // FIXME in multiSelection we currently only support transactions with one
1578   // or two splits. So we check the original transaction and extract the other
1579   // split or create it
1580   if (isMultiSelection()) {
1581     if (torig.splitCount() == 2) {
1582       foreach (const auto split, torig.splits()) {
1583         if (split.id() == sorig.id())
1584           continue;
1585         s1 = split;
1586         s1.clearId();
1587         break;
1588       }
1589     }
1590   } else {
1591     if (splits.count() == 1) {
1592       s1 = splits[0];
1593       s1.clearId();
1594     }
1595   }
1596 
1597   if (isMultiSelection() || splits.count() == 1) {
1598     auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"]);
1599     if (category && (!isMultiSelection() || !category->currentText().isEmpty())) {
1600       s1.setAccountId(category->selectedItem());
1601     }
1602 
1603     // if the first split has a memo but the second split is empty,
1604     // we just copy the memo text over
1605     if (memo) {
1606       if (!isMultiSelection() || !memo->toPlainText().isEmpty()) {
1607         // if the memo is filled, we check if the
1608         // account referenced by s1 is a regular account or a category.
1609         // in case of a regular account, we just leave the memo as is
1610         // in case of a category we simply copy the new value over the old.
1611         // in case we don't even have an account id, we just skip because
1612         // the split will be removed later on anyway.
1613         if (!s1.memo().isEmpty() && s1.memo() != s0.memo()) {
1614           if (!s1.accountId().isEmpty()) {
1615             auto acc = MyMoneyFile::instance()->account(s1.accountId());
1616             if (acc.isIncomeExpense())
1617               s1.setMemo(s0.memo());
1618             else if (KMessageBox::questionYesNo(d->m_regForm,
1619                                                 i18n("Do you want to replace memo<p><i>%1</i></p>with memo<p><i>%2</i></p>in the other split?", s1.memo(), s0.memo()), i18n("Copy memo"),
1620                                                 KStandardGuiItem::yes(), KStandardGuiItem::no(),
1621                                                 QStringLiteral("CopyMemoOver")) == KMessageBox::Yes)
1622               s1.setMemo(s0.memo());
1623           }
1624         } else {
1625           s1.setMemo(s0.memo());
1626         }
1627       }
1628     }
1629 
1630     if (updateValue && !s1.accountId().isEmpty()) {
1631       s1.setValue(-value);
1632       MyMoneyMoney shares;
1633       if (!skipPriceDialog) {
1634         if (!KCurrencyCalculator::setupSplitPrice(shares, t, s1, d->m_priceInfo, d->m_regForm))
1635           return false;
1636       } else {
1637         MyMoneyAccount cat = MyMoneyFile::instance()->account(s1.accountId());
1638         if (d->m_priceInfo.find(cat.currencyId()) != d->m_priceInfo.end()) {
1639           shares = (s1.value() * d->m_priceInfo[cat.currencyId()]).reduce().convert(cat.fraction());
1640         } else
1641           shares = s1.value();
1642       }
1643       s1.setShares(shares);
1644     }
1645 
1646     checkPayeeInSplit(s1, payeeId);
1647 
1648     if (!s1.accountId().isEmpty())
1649       t.addSplit(s1);
1650 
1651     // check if we need to add/update a VAT assignment
1652     MyMoneyFile::instance()->updateVAT(t);
1653 
1654   } else {
1655     foreach (const auto split, splits) {
1656       s1 = split;
1657       s1.clearId();
1658       checkPayeeInSplit(s1, payeeId);
1659       t.addSplit(s1);
1660     }
1661   }
1662   return true;
1663 }
1664 
setupFinalWidgets()1665 void StdTransactionEditor::setupFinalWidgets()
1666 {
1667   addFinalWidget(haveWidget("deposit"));
1668   addFinalWidget(haveWidget("payment"));
1669   addFinalWidget(haveWidget("amount"));
1670   addFinalWidget(haveWidget("status"));
1671 }
1672 
slotUpdateAccount(const QString & id)1673 void StdTransactionEditor::slotUpdateAccount(const QString& id)
1674 {
1675   Q_D(StdTransactionEditor);
1676   TransactionEditor::slotUpdateAccount(id);
1677   auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"]);
1678   if (category && category->splitButton()) {
1679     category->splitButton()->setDisabled(id.isEmpty());
1680   }
1681 }
1682