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