1 /*
2  * Copyright 2000-2003  Michael Edwardes <mte@users.sourceforge.net>
3  * Copyright 2005-2018  Thomas Baumgart <tbaumgart@kde.org>
4  * Copyright 2017-2018  Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of
9  * the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "knewaccountdlg.h"
21 
22 // ----------------------------------------------------------------------------
23 // QT Includes
24 
25 #include <QPushButton>
26 #include <QLabel>
27 #include <QButtonGroup>
28 #include <QCheckBox>
29 #include <QTabWidget>
30 #include <QRadioButton>
31 #include <QList>
32 
33 // ----------------------------------------------------------------------------
34 // KDE Headers
35 
36 #include <KMessageBox>
37 #include <KComboBox>
38 #include <kguiutils.h>
39 #include <KLocalizedString>
40 
41 // ----------------------------------------------------------------------------
42 // Project Includes
43 
44 #include "ui_knewaccountdlg.h"
45 
46 #include "kmymoneydateinput.h"
47 #include <mymoneyexception.h>
48 #include "mymoneyfile.h"
49 #include "mymoneyinstitution.h"
50 #include "mymoneyaccount.h"
51 #include "kmymoneysettings.h"
52 #include "kmymoneycurrencyselector.h"
53 #include "knewbankdlg.h"
54 #include "models.h"
55 #include "accountsmodel.h"
56 #include "hierarchyfilterproxymodel.h"
57 #include "mymoneyenums.h"
58 #include "modelenums.h"
59 
60 using namespace eMyMoney;
61 
62 class KNewAccountDlgPrivate
63 {
64   Q_DISABLE_COPY(KNewAccountDlgPrivate)
65   Q_DECLARE_PUBLIC(KNewAccountDlg)
66 
67 public:
KNewAccountDlgPrivate(KNewAccountDlg * qq)68   explicit KNewAccountDlgPrivate(KNewAccountDlg *qq) :
69     q_ptr(qq),
70     ui(new Ui::KNewAccountDlg),
71     m_filterProxyModel(nullptr),
72     m_categoryEditor(false),
73     m_isEditing(false)
74   {
75   }
76 
~KNewAccountDlgPrivate()77   ~KNewAccountDlgPrivate()
78   {
79     delete ui;
80   }
81 
init()82   void init()
83   {
84     Q_Q(KNewAccountDlg);
85     ui->setupUi(q);
86 
87     auto file = MyMoneyFile::instance();
88 
89     // initialize the m_parentAccount member
90     if (!m_account.parentAccountId().isEmpty()) {
91       try {
92         m_parentAccount = file->account(m_account.parentAccountId());
93       } catch (MyMoneyException&) {
94         m_account.setParentAccountId(QString());
95       }
96     }
97 
98     // assign a standard account if the selected parent is not set/found
99     QVector<Account::Type> filterAccountGroup {m_account.accountGroup()};
100     if (m_account.parentAccountId().isEmpty()) {
101       switch (m_account.accountGroup()) {
102         case Account::Type::Asset:
103           m_parentAccount = file->asset();
104           break;
105         case Account::Type::Liability:
106           m_parentAccount = file->liability();
107           break;
108         case Account::Type::Income:
109           m_parentAccount = file->income();
110           break;
111         case Account::Type::Expense:
112           m_parentAccount = file->expense();
113           break;
114         case Account::Type::Equity:
115           m_parentAccount = file->equity();
116           break;
117         default:
118           qDebug("Seems we have an account that hasn't been mapped to the top five");
119           if (m_categoryEditor) {
120             m_parentAccount = file->income();
121             filterAccountGroup[0] = Account::Type::Income;
122           } else {
123             m_parentAccount = file->asset();
124             filterAccountGroup[0] = Account::Type::Asset;
125           }
126       }
127     }
128 
129     ui->m_amountGroup->setId(ui->m_grossAmount, 0);
130     ui->m_amountGroup->setId(ui->m_netAmount, 1);
131 
132     // the proxy filter model
133     m_filterProxyModel = new HierarchyFilterProxyModel(q);
134     m_filterProxyModel->setHideClosedAccounts(true);
135     m_filterProxyModel->setHideEquityAccounts(!KMyMoneySettings::expertMode());
136     m_filterProxyModel->addAccountGroup(filterAccountGroup);
137     m_filterProxyModel->setCurrentAccountId(m_account.id());
138     auto const model = Models::instance()->accountsModel();
139     m_filterProxyModel->setSourceModel(model);
140     m_filterProxyModel->setSourceColumns(model->getColumns());
141     m_filterProxyModel->setDynamicSortFilter(true);
142 
143     ui->m_parentAccounts->setModel(m_filterProxyModel);
144     ui->m_parentAccounts->sortByColumn((int)eAccountsModel::Column::Account, Qt::AscendingOrder);
145 
146     ui->m_subAccountLabel->setText(i18n("Is a sub account"));
147 
148     ui->accountNameEdit->setText(m_account.name());
149     ui->descriptionEdit->setText(m_account.description());
150 
151     ui->typeCombo->setEnabled(true);
152 
153     // load the price mode combo
154     ui->m_priceMode->insertItem(i18nc("default price mode", "(default)"), 0);
155     ui->m_priceMode->insertItem(i18n("Price per share"), 1);
156     ui->m_priceMode->insertItem(i18n("Total for all shares"), 2);
157 
158     int priceMode = 0;
159     if (m_account.accountType() == Account::Type::Investment) {
160       ui->m_priceMode->setEnabled(true);
161       if (!m_account.value("priceMode").isEmpty())
162         priceMode = m_account.value("priceMode").toInt();
163     }
164     ui->m_priceMode->setCurrentItem(priceMode);
165 
166     bool haveMinBalance = false;
167     bool haveMaxCredit = false;
168     if (!m_account.openingDate().isValid()) {
169       m_account.setOpeningDate(KMyMoneySettings::firstFiscalDate());
170     }
171     ui->m_openingDateEdit->setDate(m_account.openingDate());
172 
173     handleOpeningBalanceCheckbox(m_account.currencyId());
174 
175     if (m_categoryEditor) {
176       // get rid of the tabs that are not used for categories
177       int tab = ui->m_tab->indexOf(ui->m_institutionTab);
178       if (tab != -1)
179         ui->m_tab->removeTab(tab);
180       tab = ui->m_tab->indexOf(ui->m_limitsTab);
181       if (tab != -1)
182         ui->m_tab->removeTab(tab);
183 
184       //m_qlistviewParentAccounts->setEnabled(true);
185       ui->accountNoEdit->setEnabled(false);
186 
187       ui->m_institutionBox->hide();
188       ui->m_qcheckboxNoVat->hide();
189 
190       ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Income), (int)Account::Type::Income);
191       ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Expense), (int)Account::Type::Expense);
192 
193       // Hardcoded but acceptable - if above we set the default to income do the same here
194       switch (m_account.accountType()) {
195         case Account::Type::Expense:
196           ui->typeCombo->setCurrentItem(MyMoneyAccount::accountTypeToString(Account::Type::Expense), false);
197           break;
198 
199         case Account::Type::Income:
200         default:
201           ui->typeCombo->setCurrentItem(MyMoneyAccount::accountTypeToString(Account::Type::Income), false);
202           break;
203       }
204       ui->m_currency->setEnabled(true);
205       if (m_isEditing) {
206         ui->typeCombo->setEnabled(false);
207         ui->m_currency->setDisabled(MyMoneyFile::instance()->isReferenced(m_account));
208       }
209       ui->m_qcheckboxPreferred->hide();
210 
211       ui->m_qcheckboxTax->setChecked(m_account.value("Tax").toLower() == "yes");
212       ui->m_costCenterRequiredCheckBox->setChecked(m_account.isCostCenterRequired());
213 
214       loadVatAccounts();
215     } else {
216       // get rid of the tabs that are not used for accounts
217       int taxtab = ui->m_tab->indexOf(ui->m_taxTab);
218       if (taxtab != -1) {
219           ui->m_vatCategory->setText(i18n("VAT account"));
220           ui->m_qcheckboxTax->setChecked(m_account.value("Tax") == "Yes");
221           loadVatAccounts();
222       } else {
223           ui->m_tab->removeTab(taxtab);
224       }
225 
226       ui->m_costCenterRequiredCheckBox->hide();
227 
228       switch (m_account.accountType()) {
229         case Account::Type::Savings:
230         case Account::Type::Cash:
231           haveMinBalance = true;
232           break;
233 
234         case Account::Type::Checkings:
235           haveMinBalance = true;
236           haveMaxCredit = true;
237           break;
238 
239         case Account::Type::CreditCard:
240           haveMaxCredit = true;
241           break;
242 
243         default:
244           // no limit available, so we might get rid of the tab
245           int tab = ui->m_tab->indexOf(ui->m_limitsTab);
246           if (tab != -1)
247             ui->m_tab->removeTab(tab);
248           // don't try to hide the widgets we just wiped
249           // in the next step
250           haveMaxCredit = haveMinBalance = true;
251           break;
252       }
253 
254       if (!haveMaxCredit) {
255         ui->m_maxCreditLabel->setEnabled(false);
256         ui->m_maxCreditLabel->hide();
257         ui->m_maxCreditEarlyEdit->hide();
258         ui->m_maxCreditAbsoluteEdit->hide();
259       }
260       if (!haveMinBalance) {
261         ui->m_minBalanceLabel->setEnabled(false);
262         ui->m_minBalanceLabel->hide();
263         ui->m_minBalanceEarlyEdit->hide();
264         ui->m_minBalanceAbsoluteEdit->hide();
265       }
266 
267       QString typeString = MyMoneyAccount::accountTypeToString(m_account.accountType());
268 
269       if (m_isEditing) {
270         if (m_account.isLiquidAsset()) {
271           ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Checkings), (int)Account::Type::Checkings);
272           ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Savings), (int)Account::Type::Savings);
273           ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Cash), (int)Account::Type::Cash);
274         } else {
275           ui->typeCombo->addItem(typeString, (int)m_account.accountType());
276           // Once created, accounts of other account types are not
277           // allowed to be changed.
278           ui->typeCombo->setEnabled(false);
279         }
280         // Once created, a currency cannot be changed if it is referenced.
281         ui->m_currency->setDisabled(MyMoneyFile::instance()->isReferenced(m_account));
282       } else {
283         ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Checkings), (int)Account::Type::Checkings);
284         ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Savings), (int)Account::Type::Savings);
285         ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Cash), (int)Account::Type::Cash);
286         ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::CreditCard), (int)Account::Type::CreditCard);
287         ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Loan), (int)Account::Type::Loan);
288         ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Investment), (int)Account::Type::Investment);
289         ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Asset), (int)Account::Type::Asset);
290         ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Liability), (int)Account::Type::Liability);
291         ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Stock), (int)Account::Type::Stock);
292         /*
293         ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::CertificateDep), (int)Account::Type::CertificateDep);
294         ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::MoneyMarket), (int)Account::Type::MoneyMarket);
295         ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Currency), (int)Account::Type::Currency);
296         */
297         // Do not create account types that are not supported
298         // by the current engine.
299         if (m_account.accountType() == Account::Type::Unknown ||
300             m_account.accountType() == Account::Type::CertificateDep ||
301             m_account.accountType() == Account::Type::MoneyMarket ||
302             m_account.accountType() == Account::Type::Currency)
303           typeString = MyMoneyAccount::accountTypeToString(Account::Type::Checkings);
304       }
305 
306       ui->typeCombo->setCurrentItem(typeString, false);
307 
308       if (m_account.isInvest())
309         ui->m_institutionBox->hide();
310 
311       ui->accountNoEdit->setText(m_account.number());
312       ui->m_qcheckboxPreferred->setChecked(m_account.value("PreferredAccount") == "Yes");
313       ui->m_qcheckboxNoVat->setChecked(m_account.value("NoVat") == "Yes");
314       loadKVP("iban", ui->ibanEdit);
315       loadKVP("minBalanceAbsolute", ui->m_minBalanceAbsoluteEdit);
316       loadKVP("minBalanceEarly", ui->m_minBalanceEarlyEdit);
317       loadKVP("maxCreditAbsolute", ui->m_maxCreditAbsoluteEdit);
318       loadKVP("maxCreditEarly", ui->m_maxCreditEarlyEdit);
319       // reverse the sign for display purposes
320       if (!ui->m_maxCreditAbsoluteEdit->text().isEmpty())
321         ui->m_maxCreditAbsoluteEdit->setValue(ui->m_maxCreditAbsoluteEdit->value()*MyMoneyMoney::MINUS_ONE);
322       if (!ui->m_maxCreditEarlyEdit->text().isEmpty())
323         ui->m_maxCreditEarlyEdit->setValue(ui->m_maxCreditEarlyEdit->value()*MyMoneyMoney::MINUS_ONE);
324       loadKVP("lastNumberUsed", ui->m_lastCheckNumberUsed);
325 
326       if (m_account.isInvest()) {
327         ui->typeCombo->setEnabled(false);
328         ui->m_qcheckboxPreferred->hide();
329         ui->m_currencyText->hide();
330         ui->m_currency->hide();
331       } else {
332         // use the old field and override a possible new value
333         if (!MyMoneyMoney(m_account.value("minimumBalance")).isZero()) {
334           ui->m_minBalanceAbsoluteEdit->setValue(MyMoneyMoney(m_account.value("minimumBalance")));
335         }
336       }
337 
338   //    ui->m_qcheckboxTax->hide(); TODO should only be visible for VAT category/account
339     }
340 
341     ui->m_currency->setSecurity(file->currency(m_account.currencyId()));
342 
343     // Load the institutions
344     // then the accounts
345     QString institutionName;
346 
347     try {
348       if (m_isEditing && !m_account.institutionId().isEmpty())
349         institutionName = file->institution(m_account.institutionId()).name();
350       else
351         institutionName.clear();
352     } catch (const MyMoneyException &e) {
353       qDebug("exception in init for account dialog: %s", e.what());
354     }
355 
356     if (m_account.isInvest())
357       ui->m_parentAccounts->setEnabled(false);
358 
359     if (!m_categoryEditor)
360       q->slotLoadInstitutions(institutionName);
361 
362     ui->accountNameEdit->setFocus();
363 
364     q->connect(ui->buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject);
365     q->connect(ui->buttonBox, &QDialogButtonBox::accepted, q, &KNewAccountDlg::okClicked);
366     q->connect(ui->m_parentAccounts->selectionModel(), &QItemSelectionModel::selectionChanged,
367             q, &KNewAccountDlg::slotSelectionChanged);
368     q->connect(ui->m_qbuttonNew, &QAbstractButton::clicked, q, &KNewAccountDlg::slotNewClicked);
369     q->connect(ui->typeCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), q, &KNewAccountDlg::slotAccountTypeChanged);
370 
371     q->connect(ui->accountNameEdit, &QLineEdit::textChanged, q, &KNewAccountDlg::slotCheckFinished);
372 
373     q->connect(ui->m_vatCategory,   &QAbstractButton::toggled,       q, &KNewAccountDlg::slotVatChanged);
374     q->connect(ui->m_vatAssignment, &QAbstractButton::toggled,       q, &KNewAccountDlg::slotVatAssignmentChanged);
375     q->connect(ui->m_vatCategory,   &QAbstractButton::toggled,       q, &KNewAccountDlg::slotCheckFinished);
376     q->connect(ui->m_vatAssignment, &QAbstractButton::toggled,       q, &KNewAccountDlg::slotCheckFinished);
377     q->connect(ui->m_vatRate,       &AmountEdit::textChanged,      q, &KNewAccountDlg::slotCheckFinished);
378     q->connect(ui->m_vatAccount,    &KMyMoneySelector::stateChanged, q, &KNewAccountDlg::slotCheckFinished);
379     q->connect(ui->m_currency, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), q, &KNewAccountDlg::slotCheckCurrency);
380 
381     q->connect(ui->m_minBalanceEarlyEdit,     &AmountEdit::valueChanged, q, &KNewAccountDlg::slotAdjustMinBalanceAbsoluteEdit);
382     q->connect(ui->m_minBalanceAbsoluteEdit,  &AmountEdit::valueChanged, q, &KNewAccountDlg::slotAdjustMinBalanceEarlyEdit);
383     q->connect(ui->m_maxCreditEarlyEdit,      &AmountEdit::valueChanged, q, &KNewAccountDlg::slotAdjustMaxCreditAbsoluteEdit);
384     q->connect(ui->m_maxCreditAbsoluteEdit,   &AmountEdit::valueChanged, q, &KNewAccountDlg::slotAdjustMaxCreditEarlyEdit);
385 
386     q->connect(ui->m_qcomboboxInstitutions, static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::activated), q, &KNewAccountDlg::slotLoadInstitutions);
387 
388     QModelIndex parentIndex;
389     if (!m_parentAccount.id().isEmpty()) {
390       const auto baseIdx = model->accountById(m_parentAccount.id());
391       parentIndex = m_filterProxyModel->mapFromSource(baseIdx);
392     }
393     selectParentAccount(parentIndex);
394 
395     ui->m_vatCategory->setChecked(false);
396     ui->m_vatAssignment->setChecked(false);
397 
398     // make sure our account does not have an id and no parent assigned
399     // and certainly no children in case we create a new account
400     if (!m_isEditing) {
401       m_account.clearId();
402       m_account.setParentAccountId(QString());
403       m_account.removeAccountIds();
404     } else {
405       if (!m_account.value("VatRate").isEmpty()) {
406         ui->m_vatCategory->setChecked(true);
407         ui->m_vatRate->setValue(MyMoneyMoney(m_account.value("VatRate"))*MyMoneyMoney(100, 1));
408       } else {
409         if (!m_account.value("VatAccount").isEmpty()) {
410           QString accId = m_account.value("VatAccount").toLatin1();
411           try {
412             // make sure account exists
413             MyMoneyFile::instance()->account(accId);
414             ui->m_vatAssignment->setChecked(true);
415             ui->m_vatAccount->setSelected(accId);
416             ui->m_grossAmount->setChecked(true);
417             if (m_account.value("VatAmount") == "Net")
418               ui->m_netAmount->setChecked(true);
419           } catch (const MyMoneyException &) {
420           }
421         }
422       }
423     }
424     q->slotVatChanged(ui->m_vatCategory->isChecked());
425     q->slotVatAssignmentChanged(ui->m_vatAssignment->isChecked());
426     q->slotCheckFinished();
427 
428     auto requiredFields = new KMandatoryFieldGroup(q);
429     requiredFields->setOkButton(ui->buttonBox->button(QDialogButtonBox::Ok)); // button to be enabled when all fields present
430     requiredFields->add(ui->accountNameEdit);
431   }
432 
loadKVP(const QString & key,AmountEdit * widget)433   void loadKVP(const QString& key, AmountEdit* widget)
434   {
435     if (!widget)
436       return;
437 
438     if (m_account.value(key).isEmpty()) {
439       widget->setText(QString());
440     } else {
441       widget->setValue(MyMoneyMoney(m_account.value(key)));
442     }
443   }
444 
loadKVP(const QString & key,KLineEdit * widget)445   void loadKVP(const QString& key, KLineEdit* widget)
446   {
447     if (!widget)
448       return;
449 
450     widget->setText(m_account.value(key));
451   }
452 
storeKVP(const QString & key,const QString & text,const QString & value)453   void storeKVP(const QString& key, const QString& text, const QString& value)
454   {
455     if (text.isEmpty())
456       m_account.deletePair(key);
457     else
458       m_account.setValue(key, value);
459   }
460 
storeKVP(const QString & key,QCheckBox * widget)461   void storeKVP(const QString& key, QCheckBox* widget)
462   {
463     if (widget) {
464       if(widget->isChecked()) {
465         m_account.setValue(key, "Yes");;
466       } else {
467         m_account.deletePair(key);
468       }
469     }
470   }
471 
storeKVP(const QString & key,AmountEdit * widget)472   void storeKVP(const QString& key, AmountEdit* widget)
473   {
474     storeKVP(key, widget->text(), widget->text());
475   }
476 
storeKVP(const QString & key,KLineEdit * widget)477   void storeKVP(const QString& key, KLineEdit* widget)
478   {
479     storeKVP(key, widget->text(), widget->text());
480   }
481 
loadVatAccounts()482   void loadVatAccounts()
483   {
484     QList<MyMoneyAccount> list;
485     MyMoneyFile::instance()->accountList(list);
486     QList<MyMoneyAccount>::Iterator it;
487     QStringList loadListExpense;
488     QStringList loadListIncome;
489     QStringList loadListAsset;
490     QStringList loadListLiability;
491     for (it = list.begin(); it != list.end(); ++it) {
492       if (!(*it).value("VatRate").isEmpty()) {
493         if ((*it).accountType() == Account::Type::Expense)
494           loadListExpense += (*it).id();
495         else if ((*it).accountType() == Account::Type::Income)
496           loadListIncome += (*it).id();
497         else if ((*it).accountType() == Account::Type::Asset)
498           loadListAsset += (*it).id();
499         else if ((*it).accountType() == Account::Type::Liability)
500           loadListLiability += (*it).id();
501       }
502     }
503     AccountSet vatSet;
504     if (!loadListAsset.isEmpty())
505       vatSet.load(ui->m_vatAccount, i18n("Asset"), loadListAsset, true);
506     if (!loadListLiability.isEmpty())
507       vatSet.load(ui->m_vatAccount, i18n("Liability"), loadListLiability, false);
508     if (!loadListIncome.isEmpty())
509       vatSet.load(ui->m_vatAccount, i18n("Income"), loadListIncome, false);
510     if (!loadListExpense.isEmpty())
511       vatSet.load(ui->m_vatAccount, i18n("Expense"), loadListExpense, false);
512   }
513 
adjustEditWidgets(AmountEdit * dst,AmountEdit * src,char mode,int corr)514   void adjustEditWidgets(AmountEdit* dst, AmountEdit* src, char mode, int corr)
515   {
516     MyMoneyMoney factor(corr, 1);
517     if (m_account.accountGroup() == Account::Type::Asset)
518       factor = -factor;
519 
520     switch (mode) {
521       case '<':
522         if (src->value()*factor < dst->value()*factor)
523           dst->setValue(src->value());
524         break;
525 
526       case '>':
527         if (src->value()*factor > dst->value()*factor)
528           dst->setValue(src->value());
529         break;
530     }
531   }
532 
handleOpeningBalanceCheckbox(const QString & currencyId)533   void handleOpeningBalanceCheckbox(const QString &currencyId)
534   {
535     if (m_account.accountType() == Account::Type::Equity) {
536       // check if there is another opening balance account with the same currency
537       bool isOtherOpenBalancingAccount = false;
538       QList<MyMoneyAccount> list;
539       MyMoneyFile::instance()->accountList(list);
540       QList<MyMoneyAccount>::Iterator it;
541       for (it = list.begin(); it != list.end(); ++it) {
542         if (it->id() == m_account.id() || currencyId != it->currencyId()
543             || it->accountType() != Account::Type::Equity)
544           continue;
545         if (it->value("OpeningBalanceAccount") == "Yes") {
546           isOtherOpenBalancingAccount = true;
547           break;
548         }
549       }
550       if (!isOtherOpenBalancingAccount) {
551         bool isOpenBalancingAccount = m_account.value("OpeningBalanceAccount") == "Yes";
552         ui->m_qcheckboxOpeningBalance->setChecked(isOpenBalancingAccount);
553         if (isOpenBalancingAccount) {
554           // let only allow state change if no transactions are assigned to this account
555           bool hasTransactions = MyMoneyFile::instance()->transactionCount(m_account.id()) != 0;
556           ui->m_qcheckboxOpeningBalance->setEnabled(!hasTransactions);
557           if (hasTransactions)
558             ui->m_qcheckboxOpeningBalance->setToolTip(i18n("Option has been disabled because there are transactions assigned to this account"));
559         }
560       } else {
561         ui->m_qcheckboxOpeningBalance->setChecked(false);
562         ui->m_qcheckboxOpeningBalance->setEnabled(false);
563         ui->m_qcheckboxOpeningBalance->setToolTip(i18n("Option has been disabled because there is another account flagged to be an opening balance account for this currency"));
564       }
565     } else {
566       ui->m_qcheckboxOpeningBalance->setVisible(false);
567     }
568   }
569 
selectParentAccount(const QModelIndex & parentIndex)570   void selectParentAccount(const QModelIndex& parentIndex)
571   {
572     ui->m_parentAccounts->expand(parentIndex);
573     ui->m_parentAccounts->selectionModel()->select(parentIndex, QItemSelectionModel::SelectCurrent);
574     ui->m_parentAccounts->setCurrentIndex(parentIndex);
575     ui->m_parentAccounts->scrollTo(parentIndex, QAbstractItemView::PositionAtCenter);
576   }
577 
578   KNewAccountDlg             *q_ptr;
579   Ui::KNewAccountDlg         *ui;
580   MyMoneyAccount              m_account;
581   MyMoneyAccount              m_parentAccount;
582   HierarchyFilterProxyModel  *m_filterProxyModel;
583 
584   bool m_categoryEditor;
585   bool m_isEditing;
586 };
587 
KNewAccountDlg(const MyMoneyAccount & account,bool isEditing,bool categoryEditor,QWidget * parent,const QString & title)588 KNewAccountDlg::KNewAccountDlg(const MyMoneyAccount& account, bool isEditing, bool categoryEditor, QWidget *parent, const QString& title) :
589   QDialog(parent),
590   d_ptr(new KNewAccountDlgPrivate(this))
591 {
592   Q_D(KNewAccountDlg);
593   d->m_account = account;
594   d->m_categoryEditor = categoryEditor;
595   d->m_isEditing = isEditing;
596   d->init();
597   if (!title.isEmpty())
598     setWindowTitle(title);
599 }
600 
openingBalance() const601 MyMoneyMoney KNewAccountDlg::openingBalance() const
602 {
603   Q_D(const KNewAccountDlg);
604   return d->ui->m_openingBalanceEdit->value();
605 }
606 
setOpeningBalance(const MyMoneyMoney & balance)607 void KNewAccountDlg::setOpeningBalance(const MyMoneyMoney& balance)
608 {
609   Q_D(KNewAccountDlg);
610   d->ui->m_openingBalanceEdit->setValue(balance);
611 }
612 
setOpeningBalanceShown(bool shown)613 void KNewAccountDlg::setOpeningBalanceShown(bool shown)
614 {
615   Q_D(KNewAccountDlg);
616   d->ui->m_openingBalanceLabel->setVisible(shown);
617   d->ui->m_openingBalanceEdit->setVisible(shown);
618 }
619 
setOpeningDateShown(bool shown)620 void KNewAccountDlg::setOpeningDateShown(bool shown)
621 {
622   Q_D(KNewAccountDlg);
623   d->ui->m_openingDateLabel->setVisible(shown);
624   d->ui->m_openingDateEdit->setVisible(shown);
625 }
626 
okClicked()627 void KNewAccountDlg::okClicked()
628 {
629   Q_D(KNewAccountDlg);
630   auto file = MyMoneyFile::instance();
631 
632   QString accountNameText = d->ui->accountNameEdit->text();
633   if (accountNameText.isEmpty()) {
634     KMessageBox::error(this, i18n("You have not specified a name.\nPlease fill in this field."));
635     d->ui->accountNameEdit->setFocus();
636     return;
637   }
638 
639   MyMoneyAccount parent = parentAccount();
640   if (parent.name().length() == 0) {
641     KMessageBox::error(this, i18n("Please select a parent account."));
642     return;
643   }
644 
645   if (!d->m_categoryEditor) {
646     QString institutionNameText = d->ui->m_qcomboboxInstitutions->currentText();
647     if (institutionNameText != i18n("(No Institution)")) {
648       try {
649         QList<MyMoneyInstitution> list = file->institutionList();
650         QList<MyMoneyInstitution>::ConstIterator institutionIterator;
651         for (institutionIterator = list.constBegin(); institutionIterator != list.constEnd(); ++institutionIterator) {
652           if ((*institutionIterator).name() == institutionNameText)
653             d->m_account.setInstitutionId((*institutionIterator).id());
654         }
655       } catch (const MyMoneyException &e) {
656         qDebug("Exception in account institution set: %s", e.what());
657       }
658     } else {
659       d->m_account.setInstitutionId(QString());
660     }
661   }
662 
663   d->m_account.setName(accountNameText);
664   d->m_account.setNumber(d->ui->accountNoEdit->text());
665   d->storeKVP("iban", d->ui->ibanEdit);
666   d->storeKVP("minBalanceAbsolute", d->ui->m_minBalanceAbsoluteEdit);
667   d->storeKVP("minBalanceEarly", d->ui->m_minBalanceEarlyEdit);
668 
669   // the figures for credit line with reversed sign
670   if (!d->ui->m_maxCreditAbsoluteEdit->text().isEmpty())
671     d->ui->m_maxCreditAbsoluteEdit->setValue(d->ui->m_maxCreditAbsoluteEdit->value()*MyMoneyMoney::MINUS_ONE);
672   if (!d->ui->m_maxCreditEarlyEdit->text().isEmpty())
673     d->ui->m_maxCreditEarlyEdit->setValue(d->ui->m_maxCreditEarlyEdit->value()*MyMoneyMoney::MINUS_ONE);
674   d->storeKVP("maxCreditAbsolute", d->ui->m_maxCreditAbsoluteEdit);
675   d->storeKVP("maxCreditEarly", d->ui->m_maxCreditEarlyEdit);
676   if (!d->ui->m_maxCreditAbsoluteEdit->text().isEmpty())
677     d->ui->m_maxCreditAbsoluteEdit->setValue(d->ui->m_maxCreditAbsoluteEdit->value()*MyMoneyMoney::MINUS_ONE);
678   if (!d->ui->m_maxCreditEarlyEdit->text().isEmpty())
679     d->ui->m_maxCreditEarlyEdit->setValue(d->ui->m_maxCreditEarlyEdit->value()*MyMoneyMoney::MINUS_ONE);
680 
681   d->storeKVP("lastNumberUsed", d->ui->m_lastCheckNumberUsed);
682   // delete a previous version of the minimumbalance information
683   d->storeKVP("minimumBalance", QString(), QString());
684 
685   Account::Type acctype;
686   if (!d->m_categoryEditor) {
687     acctype = static_cast<Account::Type>(d->ui->typeCombo->currentData().toInt());
688     // If it's a loan, check if the parent is asset or liability. In
689     // case of asset, we change the account type to be AssetLoan
690     if (acctype == Account::Type::Loan
691         && parent.accountGroup() == Account::Type::Asset)
692       acctype = Account::Type::AssetLoan;
693   } else {
694     acctype = parent.accountGroup();
695     QString newName;
696     if (!MyMoneyFile::instance()->isStandardAccount(parent.id())) {
697       newName = MyMoneyFile::instance()->accountToCategory(parent.id()) + MyMoneyFile::AccountSeparator;
698     }
699     newName += accountNameText;
700     if (!file->categoryToAccount(newName, acctype).isEmpty()
701         && (file->categoryToAccount(newName, acctype) != d->m_account.id())) {
702       KMessageBox::error(this, QString("<qt>") + i18n("A category named <b>%1</b> already exists. You cannot create a second category with the same name.", newName) + QString("</qt>"));
703       return;
704     }
705   }
706   d->m_account.setAccountType(acctype);
707 
708   d->m_account.setDescription(d->ui->descriptionEdit->toPlainText());
709 
710   d->m_account.setOpeningDate(d->ui->m_openingDateEdit->date());
711 
712   if (!d->m_categoryEditor) {
713     d->m_account.setCurrencyId(d->ui->m_currency->security().id());
714 
715     d->storeKVP("PreferredAccount", d->ui->m_qcheckboxPreferred);
716     d->storeKVP("NoVat", d->ui->m_qcheckboxNoVat);
717 
718     if (d->ui->m_minBalanceAbsoluteEdit->isVisible()) {
719       d->m_account.setValue("minimumBalance", d->ui->m_minBalanceAbsoluteEdit->value().toString());
720     }
721   } else {
722     if (KMyMoneySettings::hideUnusedCategory() && !d->m_isEditing) {
723       KMessageBox::information(this, i18n("You have selected to suppress the display of unused categories in the KMyMoney configuration dialog. The category you just created will therefore only be shown if it is used. Otherwise, it will be hidden in the accounts/categories view."), i18n("Hidden categories"), "NewHiddenCategory");
724     }
725     d->m_account.setCostCenterRequired(d->ui->m_costCenterRequiredCheckBox->isChecked());
726   }
727 
728   d->storeKVP("Tax", d->ui->m_qcheckboxTax);
729 
730   if (d->ui->m_qcheckboxOpeningBalance->isChecked())
731     d->m_account.setValue("OpeningBalanceAccount", "Yes");
732   else
733     d->m_account.deletePair("OpeningBalanceAccount");
734 
735   d->m_account.deletePair("VatAccount");
736   d->m_account.deletePair("VatAmount");
737   d->m_account.deletePair("VatRate");
738 
739   if (d->ui->m_vatCategory->isChecked()) {
740     d->m_account.setValue("VatRate", (d->ui->m_vatRate->value().abs() / MyMoneyMoney(100, 1)).toString());
741   } else {
742     if (d->ui->m_vatAssignment->isChecked() && !d->ui->m_vatAccount->selectedItems().isEmpty()) {
743       d->m_account.setValue("VatAccount", d->ui->m_vatAccount->selectedItems().first());
744       if (d->ui->m_netAmount->isChecked())
745         d->m_account.setValue("VatAmount", "Net");
746     }
747   }
748 
749   accept();
750 }
751 
752 
account()753 MyMoneyAccount KNewAccountDlg::account()
754 {
755   Q_D(KNewAccountDlg);
756   // assign the right currency to the account
757   d->m_account.setCurrencyId(d->ui->m_currency->security().id());
758 
759   // and the price mode
760   switch (d->ui->m_priceMode->currentItem()) {
761     case 0:
762       d->m_account.deletePair("priceMode");
763       break;
764     case 1:
765     case 2:
766       d->m_account.setValue("priceMode", QString("%1").arg(d->ui->m_priceMode->currentItem()));
767       break;
768   }
769 
770   return d->m_account;
771 }
772 
parentAccount() const773 MyMoneyAccount KNewAccountDlg::parentAccount() const
774 {
775   Q_D(const KNewAccountDlg);
776   return d->m_parentAccount;
777 }
778 
slotSelectionChanged(const QItemSelection & current,const QItemSelection & previous)779 void KNewAccountDlg::slotSelectionChanged(const QItemSelection &current, const QItemSelection &previous)
780 {
781   Q_UNUSED(previous)
782   Q_D(KNewAccountDlg);
783   if (!current.indexes().empty()) {
784     QVariant account = d->ui->m_parentAccounts->model()->data(current.indexes().front(), (int)eAccountsModel::Role::Account);
785     if (account.isValid()) {
786       d->m_parentAccount = account.value<MyMoneyAccount>();
787       d->ui->m_subAccountLabel->setText(i18n("Is a sub account of %1", d->m_parentAccount.name()));
788     }
789   }
790 }
791 
slotLoadInstitutions(const QString & name)792 void KNewAccountDlg::slotLoadInstitutions(const QString& name)
793 {
794   Q_D(KNewAccountDlg);
795   d->ui->m_qcomboboxInstitutions->clear();
796   // Are we forcing the user to use institutions?
797   d->ui->m_qcomboboxInstitutions->addItem(i18n("(No Institution)"));
798   d->ui->m_bicValue->setText(" ");
799   d->ui->ibanEdit->setEnabled(false);
800   d->ui->accountNoEdit->setEnabled(false);
801   try {
802     auto file = MyMoneyFile::instance();
803 
804     QList<MyMoneyInstitution> list = file->institutionList();
805     QList<MyMoneyInstitution>::ConstIterator institutionIterator;
806     for (institutionIterator = list.constBegin(); institutionIterator != list.constEnd(); ++institutionIterator) {
807       if ((*institutionIterator).name() == name) {
808         d->ui->ibanEdit->setEnabled(true);
809         d->ui->accountNoEdit->setEnabled(true);
810         d->ui->m_bicValue->setText((*institutionIterator).value("bic"));
811       }
812       d->ui->m_qcomboboxInstitutions->addItem((*institutionIterator).name());
813     }
814 
815     d->ui->m_qcomboboxInstitutions->setCurrentItem(name, false);
816   } catch (const MyMoneyException &e) {
817     qDebug("Exception in institution load: %s", e.what());
818   }
819 }
820 
slotNewClicked()821 void KNewAccountDlg::slotNewClicked()
822 {
823   MyMoneyInstitution institution;
824 
825   QPointer<KNewBankDlg> dlg = new KNewBankDlg(institution, this);
826   if (dlg->exec()) {
827     MyMoneyFileTransaction ft;
828     try {
829       auto file = MyMoneyFile::instance();
830 
831       institution = dlg->institution();
832       file->addInstitution(institution);
833       ft.commit();
834       slotLoadInstitutions(institution.name());
835     } catch (const MyMoneyException &) {
836       KMessageBox::information(this, i18n("Cannot add institution"));
837     }
838   }
839   delete dlg;
840 }
841 
slotAccountTypeChanged(int index)842 void KNewAccountDlg::slotAccountTypeChanged(int index)
843 {
844   Q_D(KNewAccountDlg);
845   Account::Type oldType;
846 
847   auto type = d->ui->typeCombo->itemData(index).value<Account::Type>();
848   try {
849     oldType = d->m_account.accountType();
850     if (oldType != type) {
851       d->m_account.setAccountType(type);
852       // update the account group displayed in the accounts hierarchy
853       d->m_filterProxyModel->clear();
854       d->m_filterProxyModel->addAccountGroup(QVector<Account::Type> {d->m_account.accountGroup()});
855       d->selectParentAccount(d->m_filterProxyModel->index(0, 0));
856 
857     }
858   } catch (const MyMoneyException &) {
859     qWarning("Unexpected exception in KNewAccountDlg::slotAccountTypeChanged()");
860   }
861 }
862 
slotCheckFinished()863 void KNewAccountDlg::slotCheckFinished()
864 {
865   Q_D(KNewAccountDlg);
866   auto showButton = true;
867 
868   if (d->ui->accountNameEdit->text().length() == 0) {
869     showButton = false;
870   }
871 
872   if (d->ui->m_vatCategory->isChecked() && d->ui->m_vatRate->value() <= MyMoneyMoney()) {
873     showButton = false;
874   } else {
875     if (d->ui->m_vatAssignment->isChecked() && d->ui->m_vatAccount->selectedItems().isEmpty())
876       showButton = false;
877   }
878   d->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(showButton);
879 }
880 
slotVatChanged(bool state)881 void KNewAccountDlg::slotVatChanged(bool state)
882 {
883   Q_D(KNewAccountDlg);
884   if (state) {
885     d->ui->m_vatCategoryFrame->show();
886     d->ui->m_vatAssignmentFrame->hide();
887   } else {
888     d->ui->m_vatCategoryFrame->hide();
889     if (!d->m_account.isAssetLiability()) {
890       d->ui->m_vatAssignmentFrame->show();
891     }
892   }
893 }
894 
slotVatAssignmentChanged(bool state)895 void KNewAccountDlg::slotVatAssignmentChanged(bool state)
896 {
897   Q_D(KNewAccountDlg);
898   d->ui->m_vatAccount->setEnabled(state);
899   d->ui->m_amountGroupBox->setEnabled(state);
900 }
901 
slotAdjustMinBalanceAbsoluteEdit(const QString &)902 void KNewAccountDlg::slotAdjustMinBalanceAbsoluteEdit(const QString&)
903 {
904   Q_D(KNewAccountDlg);
905   d->adjustEditWidgets(d->ui->m_minBalanceAbsoluteEdit, d->ui->m_minBalanceEarlyEdit, '<', -1);
906 }
907 
slotAdjustMinBalanceEarlyEdit(const QString &)908 void KNewAccountDlg::slotAdjustMinBalanceEarlyEdit(const QString&)
909 {
910   Q_D(KNewAccountDlg);
911   d->adjustEditWidgets(d->ui->m_minBalanceEarlyEdit, d->ui->m_minBalanceAbsoluteEdit, '>', -1);
912 }
913 
slotAdjustMaxCreditAbsoluteEdit(const QString &)914 void KNewAccountDlg::slotAdjustMaxCreditAbsoluteEdit(const QString&)
915 {
916   Q_D(KNewAccountDlg);
917   d->adjustEditWidgets(d->ui->m_maxCreditAbsoluteEdit, d->ui->m_maxCreditEarlyEdit, '>', 1);
918 }
919 
slotAdjustMaxCreditEarlyEdit(const QString &)920 void KNewAccountDlg::slotAdjustMaxCreditEarlyEdit(const QString&)
921 {
922   Q_D(KNewAccountDlg);
923   d->adjustEditWidgets(d->ui->m_maxCreditEarlyEdit, d->ui->m_maxCreditAbsoluteEdit, '<', 1);
924 }
925 
slotCheckCurrency(int index)926 void KNewAccountDlg::slotCheckCurrency(int index)
927 {
928   Q_D(KNewAccountDlg);
929   Q_UNUSED(index)
930   d->handleOpeningBalanceCheckbox(d->ui->m_currency->security().id());
931 }
932 
addTab(QWidget * w,const QString & name)933 void KNewAccountDlg::addTab(QWidget* w, const QString& name)
934 {
935   Q_D(KNewAccountDlg);
936   if (w) {
937     w->setParent(d->ui->m_tab);
938     d->ui->m_tab->addTab(w, name);
939   }
940 }
941 
newCategory(MyMoneyAccount & account,const MyMoneyAccount & parent)942 void KNewAccountDlg::newCategory(MyMoneyAccount& account, const MyMoneyAccount& parent)
943 {
944   if (KMessageBox::questionYesNo(nullptr,
945                                  QString::fromLatin1("<qt>%1</qt>").arg(i18n("<p>The category <b>%1</b> currently does not exist. Do you want to create it?</p><p><i>The parent account will default to <b>%2</b> but can be changed in the following dialog</i>.</p>", account.name(), parent.name())), i18n("Create category"),
946                                  KStandardGuiItem::yes(), KStandardGuiItem::no(), "CreateNewCategories") == KMessageBox::Yes) {
947     KNewAccountDlg::createCategory(account, parent);
948   } else {
949     // we should not keep the 'no' setting because that can confuse people like
950     // I have seen in some usability tests. So we just delete it right away.
951     KSharedConfigPtr kconfig = KSharedConfig::openConfig();
952     if (kconfig) {
953       kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("CreateNewCategories"));
954     }
955   }
956 }
957 
createCategory(MyMoneyAccount & account,const MyMoneyAccount & parent)958 void KNewAccountDlg::createCategory(MyMoneyAccount& account, const MyMoneyAccount& parent)
959 {
960   if (!parent.id().isEmpty()) {
961     try {
962       // make sure parent account exists
963       MyMoneyFile::instance()->account(parent.id());
964       account.setParentAccountId(parent.id());
965       account.setAccountType(parent.accountType());
966     } catch (const MyMoneyException &) {
967     }
968   }
969 
970   QPointer<KNewAccountDlg> dialog =
971     new KNewAccountDlg(account, false, true, 0, i18n("Create a new Category"));
972 
973   dialog->setOpeningBalanceShown(false);
974   dialog->setOpeningDateShown(false);
975 
976   if (dialog->exec() == QDialog::Accepted && dialog != 0) {
977     MyMoneyAccount parentAccount, brokerageAccount;
978     account = dialog->account();
979     parentAccount = dialog->parentAccount();
980 
981     MyMoneyFile::instance()->createAccount(account, parentAccount, brokerageAccount, MyMoneyMoney());
982   }
983   delete dialog;
984 }
985