1 /***************************************************************************
2                           newtransactioneditor.cpp
3                              -------------------
4     begin                : Sat Aug 8 2015
5     copyright            : (C) 2015 by Thomas Baumgart
6     email                : Thomas Baumgart <tbaumgart@kde.org>
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "newtransactioneditor.h"
19 
20 // ----------------------------------------------------------------------------
21 // QT Includes
22 
23 #include <QCompleter>
24 #include <QSortFilterProxyModel>
25 #include <QStringList>
26 #include <QDebug>
27 #include <QGlobalStatic>
28 #include <QStandardItemModel>
29 #include <QAbstractItemView>
30 
31 // ----------------------------------------------------------------------------
32 // KDE Includes
33 
34 #include <KLocalizedString>
35 
36 // ----------------------------------------------------------------------------
37 // Project Includes
38 
39 #include "creditdebithelper.h"
40 #include "mymoneyfile.h"
41 #include "mymoneyaccount.h"
42 #include "mymoneyexception.h"
43 #include "kmymoneyutils.h"
44 #include "kmymoneyaccountcombo.h"
45 #include "models.h"
46 #include "accountsmodel.h"
47 #include "costcentermodel.h"
48 #include "ledgermodel.h"
49 #include "splitmodel.h"
50 #include "payeesmodel.h"
51 #include "mymoneysplit.h"
52 #include "mymoneytransaction.h"
53 #include "ui_newtransactioneditor.h"
54 #include "splitdialog.h"
55 #include "widgethintframe.h"
56 #include "icons/icons.h"
57 #include "modelenums.h"
58 #include "mymoneyenums.h"
59 
60 using namespace Icons;
61 
62 Q_GLOBAL_STATIC(QDate, lastUsedPostDate)
63 
64 class NewTransactionEditor::Private
65 {
66 public:
Private(NewTransactionEditor * parent)67   Private(NewTransactionEditor* parent)
68   : ui(new Ui_NewTransactionEditor)
69   , accountsModel(new AccountNamesFilterProxyModel(parent))
70   , costCenterModel(new QSortFilterProxyModel(parent))
71   , payeesModel(new QSortFilterProxyModel(parent))
72   , accepted(false)
73   , costCenterRequired(false)
74   , amountHelper(nullptr)
75   {
76     accountsModel->setObjectName("NewTransactionEditor::accountsModel");
77     costCenterModel->setObjectName("SortedCostCenterModel");
78     payeesModel->setObjectName("SortedPayeesModel");
79     statusModel.setObjectName("StatusModel");
80     splitModel.setObjectName("SplitModel");
81 
82     costCenterModel->setSortLocaleAware(true);
83     costCenterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
84 
85     payeesModel->setSortLocaleAware(true);
86     payeesModel->setSortCaseSensitivity(Qt::CaseInsensitive);
87 
88     createStatusEntry(eMyMoney::Split::State::NotReconciled);
89     createStatusEntry(eMyMoney::Split::State::Cleared);
90     createStatusEntry(eMyMoney::Split::State::Reconciled);
91     // createStatusEntry(eMyMoney::Split::State::Frozen);
92   }
93 
~Private()94   ~Private()
95   {
96     delete ui;
97   }
98 
99   void createStatusEntry(eMyMoney::Split::State status);
100   void updateWidgetState();
101   bool checkForValidTransaction(bool doUserInteraction = true);
102   bool isDatePostOpeningDate(const QDate& date, const QString& accountId);
103 
104   bool postdateChanged(const QDate& date);
105   bool costCenterChanged(int costCenterIndex);
106   bool categoryChanged(const QString& accountId);
107   bool numberChanged(const QString& newNumber);
108   bool valueChanged(CreditDebitHelper* valueHelper);
109 
110   Ui_NewTransactionEditor*      ui;
111   AccountNamesFilterProxyModel* accountsModel;
112   QSortFilterProxyModel*        costCenterModel;
113   QSortFilterProxyModel*        payeesModel;
114   bool                          accepted;
115   bool                          costCenterRequired;
116   SplitModel                    splitModel;
117   QStandardItemModel            statusModel;
118   QString                       transactionSplitId;
119   MyMoneyAccount                m_account;
120   MyMoneyTransaction            transaction;
121   MyMoneySplit                  split;
122   CreditDebitHelper*            amountHelper;
123 };
124 
createStatusEntry(eMyMoney::Split::State status)125 void NewTransactionEditor::Private::createStatusEntry(eMyMoney::Split::State status)
126 {
127   QStandardItem* p = new QStandardItem(KMyMoneyUtils::reconcileStateToString(status, true));
128   p->setData((int)status);
129   statusModel.appendRow(p);
130 }
131 
updateWidgetState()132 void NewTransactionEditor::Private::updateWidgetState()
133 {
134   // just in case it is disabled we turn it on
135   ui->costCenterCombo->setEnabled(true);
136 
137   // setup the category/account combo box. If we have a split transaction, we disable the
138   // combo box altogether. Changes can only be made via the split dialog editor
139   bool blocked = false;
140   QModelIndex index;
141 
142   // update the category combo box
143   ui->accountCombo->setEnabled(true);
144   switch(splitModel.rowCount()) {
145     case 0:
146       ui->accountCombo->setSelected(QString());
147       break;
148     case 1:
149       index = splitModel.index(0, 0);
150       ui->accountCombo->setSelected(splitModel.data(index, (int)eLedgerModel::Role::AccountId).toString());
151       break;
152     default:
153       index = splitModel.index(0, 0);
154       blocked = ui->accountCombo->lineEdit()->blockSignals(true);
155       ui->accountCombo->lineEdit()->setText(i18n("Split transaction"));
156       ui->accountCombo->setDisabled(true);
157       ui->accountCombo->lineEdit()->blockSignals(blocked);
158       ui->costCenterCombo->setDisabled(true);
159       ui->costCenterLabel->setDisabled(true);
160       break;
161   }
162   ui->accountCombo->hidePopup();
163 
164   // update the costcenter combo box
165   if(ui->costCenterCombo->isEnabled()) {
166     // extract the cost center
167     index = splitModel.index(0, 0);
168     QModelIndexList ccList = costCenterModel->match(costCenterModel->index(0, 0), CostCenterModel::CostCenterIdRole,
169                                                     splitModel.data(index, (int)eLedgerModel::Role::CostCenterId),
170                                       1,
171                                       Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive));
172     if (ccList.count() > 0) {
173       index = ccList.front();
174       ui->costCenterCombo->setCurrentIndex(index.row());
175     }
176   }
177 }
178 
checkForValidTransaction(bool doUserInteraction)179 bool NewTransactionEditor::Private::checkForValidTransaction(bool doUserInteraction)
180 {
181   QStringList infos;
182   bool rc = true;
183   if(!postdateChanged(ui->dateEdit->date())) {
184     infos << ui->dateEdit->toolTip();
185     rc = false;
186   }
187 
188   if(!costCenterChanged(ui->costCenterCombo->currentIndex())) {
189     infos << ui->costCenterCombo->toolTip();
190     rc = false;
191   }
192 
193   if(doUserInteraction) {
194     /// @todo add dialog here that shows the @a infos about the problem
195   }
196   return rc;
197 }
198 
isDatePostOpeningDate(const QDate & date,const QString & accountId)199 bool NewTransactionEditor::Private::isDatePostOpeningDate(const QDate& date, const QString& accountId)
200 {
201   bool rc = true;
202 
203   try {
204     MyMoneyAccount account = MyMoneyFile::instance()->account(accountId);
205     const bool isIncomeExpense = account.isIncomeExpense();
206 
207     // we don't check for categories
208     if(!isIncomeExpense) {
209       if(date < account.openingDate())
210         rc = false;
211     }
212   } catch (MyMoneyException &e) {
213     qDebug() << "Ooops: invalid account id" << accountId << "in" << Q_FUNC_INFO;
214   }
215   return rc;
216 }
217 
postdateChanged(const QDate & date)218 bool NewTransactionEditor::Private::postdateChanged(const QDate& date)
219 {
220   bool rc = true;
221   WidgetHintFrame::hide(ui->dateEdit, i18n("The posting date of the transaction."));
222 
223   // collect all account ids
224   QStringList accountIds;
225   accountIds << m_account.id();
226   for(int row = 0; row < splitModel.rowCount(); ++row) {
227     QModelIndex index = splitModel.index(row, 0);
228     accountIds << splitModel.data(index, (int)eLedgerModel::Role::AccountId).toString();;
229   }
230 
231   Q_FOREACH(QString accountId, accountIds) {
232     if(!isDatePostOpeningDate(date, accountId)) {
233       MyMoneyAccount account = MyMoneyFile::instance()->account(accountId);
234       WidgetHintFrame::show(ui->dateEdit, i18n("The posting date is prior to the opening date of account <b>%1</b>.", account.name()));
235       rc = false;
236       break;
237     }
238   }
239   return rc;
240 }
241 
242 
costCenterChanged(int costCenterIndex)243 bool NewTransactionEditor::Private::costCenterChanged(int costCenterIndex)
244 {
245   bool rc = true;
246   WidgetHintFrame::hide(ui->costCenterCombo, i18n("The cost center this transaction should be assigned to."));
247   if(costCenterIndex != -1) {
248     if(costCenterRequired && ui->costCenterCombo->currentText().isEmpty()) {
249       WidgetHintFrame::show(ui->costCenterCombo, i18n("A cost center assignment is required for a transaction in the selected category."));
250       rc = false;
251     }
252     if(rc == true && splitModel.rowCount() == 1) {
253       QModelIndex index = costCenterModel->index(costCenterIndex, 0);
254       QString costCenterId = costCenterModel->data(index, CostCenterModel::CostCenterIdRole).toString();
255       index = splitModel.index(0, 0);
256       splitModel.setData(index, costCenterId, (int)eLedgerModel::Role::CostCenterId);
257     }
258   }
259 
260   return rc;
261 }
262 
categoryChanged(const QString & accountId)263 bool NewTransactionEditor::Private::categoryChanged(const QString& accountId)
264 {
265   bool rc = true;
266   if(!accountId.isEmpty() && splitModel.rowCount() <= 1) {
267     try {
268       MyMoneyAccount category = MyMoneyFile::instance()->account(accountId);
269       const bool isIncomeExpense = category.isIncomeExpense();
270       ui->costCenterCombo->setEnabled(isIncomeExpense);
271       ui->costCenterLabel->setEnabled(isIncomeExpense);
272       costCenterRequired = category.isCostCenterRequired();
273       rc &= costCenterChanged(ui->costCenterCombo->currentIndex());
274       rc &= postdateChanged(ui->dateEdit->date());
275 
276       // make sure we have a split in the model
277       bool newSplit = false;
278       if(splitModel.rowCount() == 0) {
279         splitModel.addEmptySplitEntry();
280         newSplit = true;
281       }
282 
283       const QModelIndex index = splitModel.index(0, 0);
284       splitModel.setData(index, accountId, (int)eLedgerModel::Role::AccountId);
285       if(newSplit) {
286         costCenterChanged(ui->costCenterCombo->currentIndex());
287 
288         if(amountHelper->haveValue()) {
289           splitModel.setData(index, QVariant::fromValue<MyMoneyMoney>(-amountHelper->value()), (int)eLedgerModel::Role::SplitValue);
290 
291           /// @todo make sure to convert initial value to shares according to price information
292 
293           splitModel.setData(index, QVariant::fromValue<MyMoneyMoney>(-amountHelper->value()), (int)eLedgerModel::Role::SplitShares);
294         }
295       }
296 
297       /// @todo we need to make sure to support multiple currencies here
298 
299 
300     } catch (MyMoneyException &e) {
301       qDebug() << "Ooops: invalid account id" << accountId << "in" << Q_FUNC_INFO;
302     }
303   }
304   return rc;
305 }
306 
numberChanged(const QString & newNumber)307 bool NewTransactionEditor::Private::numberChanged(const QString& newNumber)
308 {
309   bool rc = true;
310   WidgetHintFrame::hide(ui->numberEdit, i18n("The check number used for this transaction."));
311   if(!newNumber.isEmpty()) {
312     const LedgerModel* model = Models::instance()->ledgerModel();
313     QModelIndexList list = model->match(model->index(0, 0), (int)eLedgerModel::Role::Number,
314                                         QVariant(newNumber),
315                                         -1,                         // all splits
316                                         Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive));
317 
318     foreach(QModelIndex index, list) {
319       if(model->data(index, (int)eLedgerModel::Role::AccountId) == m_account.id()
320         && model->data(index, (int)eLedgerModel::Role::TransactionSplitId) != transactionSplitId) {
321         WidgetHintFrame::show(ui->numberEdit, i18n("The check number <b>%1</b> has already been used in this account.", newNumber));
322         rc = false;
323         break;
324       }
325     }
326   }
327   return rc;
328 }
329 
valueChanged(CreditDebitHelper * valueHelper)330 bool NewTransactionEditor::Private::valueChanged(CreditDebitHelper* valueHelper)
331 {
332   bool rc = true;
333   if(valueHelper->haveValue()  && splitModel.rowCount() <= 1) {
334     rc = false;
335     try {
336       MyMoneyMoney shares;
337       if(splitModel.rowCount() == 1) {
338         const QModelIndex index = splitModel.index(0, 0);
339         splitModel.setData(index, QVariant::fromValue<MyMoneyMoney>(-amountHelper->value()), (int)eLedgerModel::Role::SplitValue);
340 
341         /// @todo make sure to support multiple currencies
342 
343         splitModel.setData(index, QVariant::fromValue<MyMoneyMoney>(-amountHelper->value()), (int)eLedgerModel::Role::SplitShares);
344       } else {
345         /// @todo ask what to do: if the rest of the splits is the same amount we could simply reverse the sign
346         /// of all splits, otherwise we could ask if the user wants to start the split editor or anything else.
347       }
348       rc = true;
349 
350     } catch (MyMoneyException &e) {
351       qDebug() << "Ooops: something went wrong in" << Q_FUNC_INFO;
352     }
353   }
354   return rc;
355 }
356 
357 
NewTransactionEditor(QWidget * parent,const QString & accountId)358 NewTransactionEditor::NewTransactionEditor(QWidget* parent, const QString& accountId)
359   : QFrame(parent, Qt::FramelessWindowHint /* | Qt::X11BypassWindowManagerHint */)
360   , d(new Private(this))
361 {
362   auto const model = Models::instance()->accountsModel();
363   // extract account information from model
364   const auto index = model->accountById(accountId);
365   d->m_account = model->data(index, (int)eAccountsModel::Role::Account).value<MyMoneyAccount>();
366 
367   d->ui->setupUi(this);
368 
369   d->accountsModel->addAccountGroup(QVector<eMyMoney::Account::Type> {eMyMoney::Account::Type::Asset, eMyMoney::Account::Type::Liability, eMyMoney::Account::Type::Income, eMyMoney::Account::Type::Expense, eMyMoney::Account::Type::Equity});
370   d->accountsModel->setHideEquityAccounts(false);
371   d->accountsModel->setSourceModel(model);
372   d->accountsModel->setSourceColumns(model->getColumns());
373   d->accountsModel->sort((int)eAccountsModel::Column::Account);
374   d->ui->accountCombo->setModel(d->accountsModel);
375 
376   d->costCenterModel->setSortRole(Qt::DisplayRole);
377   d->costCenterModel->setSourceModel(Models::instance()->costCenterModel());
378   d->costCenterModel->sort(0);
379 
380   d->ui->costCenterCombo->setEditable(true);
381   d->ui->costCenterCombo->setModel(d->costCenterModel);
382   d->ui->costCenterCombo->setModelColumn(0);
383   d->ui->costCenterCombo->completer()->setFilterMode(Qt::MatchContains);
384 
385   d->payeesModel->setSortRole(Qt::DisplayRole);
386   d->payeesModel->setSourceModel(Models::instance()->payeesModel());
387   d->payeesModel->sort(0);
388 
389   d->ui->payeeEdit->setEditable(true);
390   d->ui->payeeEdit->setModel(d->payeesModel);
391   d->ui->payeeEdit->setModelColumn(0);
392   d->ui->payeeEdit->completer()->setFilterMode(Qt::MatchContains);
393 
394   d->ui->enterButton->setIcon(Icons::get(Icon::DialogOK));
395   d->ui->cancelButton->setIcon(Icons::get(Icon::DialogCancel));
396 
397   d->ui->statusCombo->setModel(&d->statusModel);
398 
399   d->ui->dateEdit->setDisplayFormat(QLocale().dateFormat(QLocale::ShortFormat));
400 
401   d->ui->amountEditCredit->setAllowEmpty(true);
402   d->ui->amountEditDebit->setAllowEmpty(true);
403   d->amountHelper = new CreditDebitHelper(this, d->ui->amountEditCredit, d->ui->amountEditDebit);
404 
405   WidgetHintFrameCollection* frameCollection = new WidgetHintFrameCollection(this);
406   frameCollection->addFrame(new WidgetHintFrame(d->ui->dateEdit));
407   frameCollection->addFrame(new WidgetHintFrame(d->ui->costCenterCombo));
408   frameCollection->addFrame(new WidgetHintFrame(d->ui->numberEdit, WidgetHintFrame::Warning));
409   frameCollection->addWidget(d->ui->enterButton);
410 
411   connect(d->ui->numberEdit, SIGNAL(textChanged(QString)), this, SLOT(numberChanged(QString)));
412   connect(d->ui->costCenterCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(costCenterChanged(int)));
413   connect(d->ui->accountCombo, SIGNAL(accountSelected(QString)), this, SLOT(categoryChanged(QString)));
414   connect(d->ui->dateEdit, SIGNAL(dateChanged(QDate)), this, SLOT(postdateChanged(QDate)));
415   connect(d->amountHelper, SIGNAL(valueChanged()), this, SLOT(valueChanged()));
416 
417   connect(d->ui->cancelButton, SIGNAL(clicked(bool)), this, SLOT(reject()));
418   connect(d->ui->enterButton, SIGNAL(clicked(bool)), this, SLOT(acceptEdit()));
419   connect(d->ui->splitEditorButton, SIGNAL(clicked(bool)), this, SLOT(editSplits()));
420 
421   // handle some events in certain conditions different from default
422   d->ui->payeeEdit->installEventFilter(this);
423   d->ui->costCenterCombo->installEventFilter(this);
424   d->ui->tagComboBox->installEventFilter(this);
425   d->ui->statusCombo->installEventFilter(this);
426 
427   // setup tooltip
428 
429   // setWindowFlags(Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint);
430 }
431 
~NewTransactionEditor()432 NewTransactionEditor::~NewTransactionEditor()
433 {
434 }
435 
accepted() const436 bool NewTransactionEditor::accepted() const
437 {
438   return d->accepted;
439 }
440 
acceptEdit()441 void NewTransactionEditor::acceptEdit()
442 {
443   if(d->checkForValidTransaction()) {
444     d->accepted = true;
445     emit done();
446   }
447 }
448 
reject()449 void NewTransactionEditor::reject()
450 {
451   emit done();
452 }
453 
keyPressEvent(QKeyEvent * e)454 void NewTransactionEditor::keyPressEvent(QKeyEvent* e)
455 {
456   if (!e->modifiers() || (e->modifiers() & Qt::KeypadModifier && e->key() == Qt::Key_Enter)) {
457     switch (e->key()) {
458     case Qt::Key_Enter:
459     case Qt::Key_Return:
460       {
461         if(focusWidget() == d->ui->cancelButton) {
462           reject();
463         } else {
464           if(d->ui->enterButton->isEnabled()) {
465             d->ui->enterButton->click();
466           }
467           return;
468         }
469       }
470       break;
471 
472     case Qt::Key_Escape:
473       reject();
474       break;
475 
476     default:
477       e->ignore();
478       return;
479     }
480   } else {
481     e->ignore();
482   }
483 }
484 
loadTransaction(const QString & id)485 void NewTransactionEditor::loadTransaction(const QString& id)
486 {
487   const LedgerModel* model = Models::instance()->ledgerModel();
488   const QString transactionId = model->transactionIdFromTransactionSplitId(id);
489 
490   if(id.isEmpty()) {
491     d->transactionSplitId.clear();
492     d->transaction = MyMoneyTransaction();
493     if(lastUsedPostDate()->isValid()) {
494       d->ui->dateEdit->setDate(*lastUsedPostDate());
495     } else {
496       d->ui->dateEdit->setDate(QDate::currentDate());
497     }
498     bool blocked = d->ui->accountCombo->lineEdit()->blockSignals(true);
499     d->ui->accountCombo->lineEdit()->clear();
500     d->ui->accountCombo->lineEdit()->blockSignals(blocked);
501 
502   } else {
503     // find which item has this id and set is as the current item
504     QModelIndexList list = model->match(model->index(0, 0), (int)eLedgerModel::Role::TransactionId,
505                                         QVariant(transactionId),
506                                         -1,                         // all splits
507                                         Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive));
508 
509     Q_FOREACH(QModelIndex index, list) {
510       // the selected split?
511       const QString transactionSplitId = model->data(index, (int)eLedgerModel::Role::TransactionSplitId).toString();
512       if(transactionSplitId == id) {
513         d->transactionSplitId = id;
514         d->transaction = model->data(index, (int)eLedgerModel::Role::Transaction).value<MyMoneyTransaction>();
515         d->split = model->data(index, (int)eLedgerModel::Role::Split).value<MyMoneySplit>();
516         d->ui->dateEdit->setDate(model->data(index, (int)eLedgerModel::Role::PostDate).toDate());
517         d->ui->payeeEdit->lineEdit()->setText(model->data(index, (int)eLedgerModel::Role::PayeeName).toString());
518         d->ui->memoEdit->clear();
519         d->ui->memoEdit->insertPlainText(model->data(index, (int)eLedgerModel::Role::Memo).toString());
520         d->ui->memoEdit->moveCursor(QTextCursor::Start);
521         d->ui->memoEdit->ensureCursorVisible();
522 
523         // The calculator for the amount field can simply be added as an icon to the line edit widget.
524         // See https://stackoverflow.com/questions/11381865/how-to-make-an-extra-icon-in-qlineedit-like-this howto do it
525         d->ui->amountEditCredit->setText(model->data(model->index(index.row(), (int)eLedgerModel::Column::Payment)).toString());
526         d->ui->amountEditDebit->setText(model->data(model->index(index.row(), (int)eLedgerModel::Column::Deposit)).toString());
527 
528         d->ui->numberEdit->setText(model->data(index, (int)eLedgerModel::Role::Number).toString());
529         d->ui->statusCombo->setCurrentIndex(model->data(index, (int)eLedgerModel::Role::Number).toInt());
530 
531         QModelIndexList stList = d->statusModel.match(d->statusModel.index(0, 0), Qt::UserRole+1, model->data(index, (int)eLedgerModel::Role::Reconciliation).toInt());
532         if(stList.count()) {
533           QModelIndex stIndex = stList.front();
534           d->ui->statusCombo->setCurrentIndex(stIndex.row());
535         }
536       } else {
537         d->splitModel.addSplit(transactionSplitId);
538       }
539     }
540     d->updateWidgetState();
541   }
542 
543   // set focus to date edit once we return to event loop
544   QMetaObject::invokeMethod(d->ui->dateEdit, "setFocus", Qt::QueuedConnection);
545 }
546 
numberChanged(const QString & newNumber)547 void NewTransactionEditor::numberChanged(const QString& newNumber)
548 {
549   d->numberChanged(newNumber);
550 }
551 
categoryChanged(const QString & accountId)552 void NewTransactionEditor::categoryChanged(const QString& accountId)
553 {
554   d->categoryChanged(accountId);
555 }
556 
costCenterChanged(int costCenterIndex)557 void NewTransactionEditor::costCenterChanged(int costCenterIndex)
558 {
559   d->costCenterChanged(costCenterIndex);
560 }
561 
postdateChanged(const QDate & date)562 void NewTransactionEditor::postdateChanged(const QDate& date)
563 {
564   d->postdateChanged(date);
565 }
566 
valueChanged()567 void NewTransactionEditor::valueChanged()
568 {
569   d->valueChanged(d->amountHelper);
570 }
571 
editSplits()572 void NewTransactionEditor::editSplits()
573 {
574   SplitModel splitModel;
575 
576   splitModel.deepCopy(d->splitModel, true);
577 
578   // create an empty split at the end
579   splitModel.addEmptySplitEntry();
580 
581   QPointer<SplitDialog> splitDialog = new SplitDialog(d->m_account, transactionAmount(), this);
582   splitDialog->setModel(&splitModel);
583 
584   int rc = splitDialog->exec();
585 
586   if(splitDialog && (rc == QDialog::Accepted)) {
587       // remove that empty split again before we update the splits
588       splitModel.removeEmptySplitEntry();
589 
590       // copy the splits model contents
591       d->splitModel.deepCopy(splitModel, true);
592 
593       // update the transaction amount
594       d->amountHelper->setValue(splitDialog->transactionAmount());
595 
596       d->updateWidgetState();
597       QWidget *next = d->ui->tagComboBox;
598       if(d->ui->costCenterCombo->isEnabled()) {
599         next = d->ui->costCenterCombo;
600       }
601       next->setFocus();
602   }
603 
604   if(splitDialog) {
605     splitDialog->deleteLater();
606   }
607 }
608 
transactionAmount() const609 MyMoneyMoney NewTransactionEditor::transactionAmount() const
610 {
611   return d->amountHelper->value();
612 }
613 
saveTransaction()614 void NewTransactionEditor::saveTransaction()
615 {
616   MyMoneyTransaction t;
617 
618   if(!d->transactionSplitId.isEmpty()) {
619     t = d->transaction;
620   } else {
621     // we keep the date when adding a new transaction
622     // for the next new one
623     *lastUsedPostDate() = d->ui->dateEdit->date();
624   }
625 
626   // first remove the splits that are gone
627   foreach (const auto split, t.splits()) {
628     if(split.id() == d->split.id()) {
629       continue;
630     }
631     int row;
632     for(row = 0; row < d->splitModel.rowCount(); ++row) {
633       QModelIndex index = d->splitModel.index(row, 0);
634       if(d->splitModel.data(index, (int)eLedgerModel::Role::SplitId).toString() == split.id()) {
635         break;
636       }
637     }
638 
639     // if the split is not in the model, we get rid of it
640     if(d->splitModel.rowCount() == row) {
641       t.removeSplit(split);
642     }
643   }
644 
645   MyMoneyFileTransaction ft;
646   try {
647     // new we update the split we are opened for
648     MyMoneySplit sp(d->split);
649     sp.setNumber(d->ui->numberEdit->text());
650     sp.setMemo(d->ui->memoEdit->toPlainText());
651     sp.setShares(d->amountHelper->value());
652     if(t.commodity().isEmpty()) {
653       t.setCommodity(d->m_account.currencyId());
654       sp.setValue(d->amountHelper->value());
655     } else {
656       /// @todo check that the transactions commodity is the same
657       /// as the one of the account this split references. If
658       /// that is not the case, the next statement would create
659       /// a problem
660       sp.setValue(d->amountHelper->value());
661     }
662 
663     if(sp.reconcileFlag() != eMyMoney::Split::State::Reconciled
664     && !sp.reconcileDate().isValid()
665     && d->ui->statusCombo->currentIndex() == (int)eMyMoney::Split::State::Reconciled) {
666       sp.setReconcileDate(QDate::currentDate());
667     }
668     sp.setReconcileFlag(static_cast<eMyMoney::Split::State>(d->ui->statusCombo->currentIndex()));
669     // sp.setPayeeId(d->ui->payeeEdit->cu)
670     if(sp.id().isEmpty()) {
671       t.addSplit(sp);
672     } else {
673       t.modifySplit(sp);
674     }
675     t.setPostDate(d->ui->dateEdit->date());
676 
677     // now update and add what we have in the model
678     const SplitModel * model = &d->splitModel;
679     for(int row = 0; row < model->rowCount(); ++row) {
680       QModelIndex index = model->index(row, 0);
681       MyMoneySplit s;
682       const QString splitId = model->data(index, (int)eLedgerModel::Role::SplitId).toString();
683       if(!SplitModel::isNewSplitId(splitId)) {
684         s = t.splitById(splitId);
685       }
686       s.setNumber(model->data(index, (int)eLedgerModel::Role::Number).toString());
687       s.setMemo(model->data(index, (int)eLedgerModel::Role::Memo).toString());
688       s.setAccountId(model->data(index, (int)eLedgerModel::Role::AccountId).toString());
689       s.setShares(model->data(index, (int)eLedgerModel::Role::SplitShares).value<MyMoneyMoney>());
690       s.setValue(model->data(index, (int)eLedgerModel::Role::SplitValue).value<MyMoneyMoney>());
691       s.setCostCenterId(model->data(index, (int)eLedgerModel::Role::CostCenterId).toString());
692       s.setPayeeId(model->data(index, (int)eLedgerModel::Role::PayeeId).toString());
693 
694       // reconcile flag and date
695       if(s.id().isEmpty()) {
696         t.addSplit(s);
697       } else {
698         t.modifySplit(s);
699       }
700     }
701 
702     if(t.id().isEmpty()) {
703       MyMoneyFile::instance()->addTransaction(t);
704     } else {
705       MyMoneyFile::instance()->modifyTransaction(t);
706     }
707     ft.commit();
708 
709   } catch (const MyMoneyException &e) {
710     qDebug() << Q_FUNC_INFO << "something went wrong" << e.what();
711   }
712 
713 }
714 
eventFilter(QObject * o,QEvent * e)715 bool NewTransactionEditor::eventFilter(QObject* o, QEvent* e)
716 {
717   auto cb = qobject_cast<QComboBox*>(o);
718   if (o) {
719     // filter out wheel events for combo boxes if the popup view is not visible
720     if ((e->type() == QEvent::Wheel) && !cb->view()->isVisible()) {
721       return true;
722     }
723   }
724   return QFrame::eventFilter(o, e);
725 }
726 
727 // kate: space-indent on; indent-width 2; remove-trailing-space on; remove-trailing-space-save on;
728