Changeset View
Changeset View
Standalone View
Standalone View
kmymoney/wizards/newaccountwizard/kloandetailspage.cpp
- This file was added.
1 | /*************************************************************************** | ||||
---|---|---|---|---|---|
2 | kloandetailspage.cpp | ||||
3 | ------------------- | ||||
4 | begin : Tue Sep 25 2006 | ||||
5 | copyright : (C) 2007 Thomas Baumgart | ||||
6 | email : Thomas Baumgart <ipwizard@users.sourceforge.net> | ||||
7 | (C) 2017 by Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> | ||||
8 | ***************************************************************************/ | ||||
9 | | ||||
10 | /*************************************************************************** | ||||
11 | * * | ||||
12 | * This program is free software; you can redistribute it and/or modify * | ||||
13 | * it under the terms of the GNU General Public License as published by * | ||||
14 | * the Free Software Foundation; either version 2 of the License, or * | ||||
15 | * (at your option) any later version. * | ||||
16 | * * | ||||
17 | ***************************************************************************/ | ||||
18 | | ||||
19 | #include "kloandetailspage.h" | ||||
20 | #include "kloandetailspage_p.h" | ||||
21 | | ||||
22 | #include <qmath.h> | ||||
23 | | ||||
24 | // ---------------------------------------------------------------------------- | ||||
25 | // QT Includes | ||||
26 | | ||||
27 | #include <QPushButton> | ||||
28 | #include <QSpinBox> | ||||
29 | | ||||
30 | // ---------------------------------------------------------------------------- | ||||
31 | // KDE Includes | ||||
32 | | ||||
33 | #include <KComboBox> | ||||
34 | #include <KLineEdit> | ||||
35 | #include <KLocalizedString> | ||||
36 | #include <KMessageBox> | ||||
37 | | ||||
38 | // ---------------------------------------------------------------------------- | ||||
39 | // Project Includes | ||||
40 | | ||||
41 | #include "ui_kgeneralloaninfopage.h" | ||||
42 | #include "ui_kloandetailspage.h" | ||||
43 | | ||||
44 | #include "kmymoneyedit.h" | ||||
45 | #include "kmymoneyfrequencycombo.h" | ||||
46 | #include "knewaccountwizard.h" | ||||
47 | #include "knewaccountwizard_p.h" | ||||
48 | #include "kgeneralloaninfopage.h" | ||||
49 | #include "kgeneralloaninfopage_p.h" | ||||
50 | #include "kloandetailspage_p.h" | ||||
51 | #include "kloanpaymentpage.h" | ||||
52 | #include "mymoneyenums.h" | ||||
53 | #include "mymoneyexception.h" | ||||
54 | #include "mymoneyfinancialcalculator.h" | ||||
55 | #include "mymoneymoney.h" | ||||
56 | #include "wizardpage.h" | ||||
57 | | ||||
58 | using namespace eMyMoney; | ||||
59 | | ||||
60 | namespace NewAccountWizard | ||||
61 | { | ||||
62 | LoanDetailsPage::LoanDetailsPage(Wizard* wizard) : | ||||
63 | QWidget(wizard), | ||||
64 | WizardPage<Wizard>(*new LoanDetailsPagePrivate(wizard), StepPayments, this, wizard) | ||||
65 | { | ||||
66 | Q_D(LoanDetailsPage); | ||||
67 | d->m_needCalculate = true; | ||||
68 | d->ui->setupUi(this); | ||||
69 | // force the balloon payment to zero (default) | ||||
70 | d->ui->m_balloonAmount->setValue(MyMoneyMoney()); | ||||
71 | // allow any precision for the interest rate | ||||
72 | d->ui->m_interestRate->setPrecision(-1); | ||||
73 | | ||||
74 | connect(d->ui->m_paymentDue, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &LoanDetailsPage::slotValuesChanged); | ||||
75 | | ||||
76 | connect(d->ui->m_termAmount, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &LoanDetailsPage::slotValuesChanged); | ||||
77 | connect(d->ui->m_termUnit, static_cast<void (QComboBox::*)(int)>(&QComboBox::highlighted), this, &LoanDetailsPage::slotValuesChanged); | ||||
78 | connect(d->ui->m_loanAmount, &KMyMoneyEdit::textChanged, this, &LoanDetailsPage::slotValuesChanged); | ||||
79 | connect(d->ui->m_interestRate, &KMyMoneyEdit::textChanged, this, &LoanDetailsPage::slotValuesChanged); | ||||
80 | connect(d->ui->m_paymentAmount, &KMyMoneyEdit::textChanged, this, &LoanDetailsPage::slotValuesChanged); | ||||
81 | connect(d->ui->m_balloonAmount, &KMyMoneyEdit::textChanged, this, &LoanDetailsPage::slotValuesChanged); | ||||
82 | | ||||
83 | connect(d->ui->m_calculateButton, &QAbstractButton::clicked, this, &LoanDetailsPage::slotCalculate); | ||||
84 | } | ||||
85 | | ||||
86 | LoanDetailsPage::~LoanDetailsPage() | ||||
87 | { | ||||
88 | } | ||||
89 | | ||||
90 | void LoanDetailsPage::enterPage() | ||||
91 | { | ||||
92 | Q_D(LoanDetailsPage); | ||||
93 | // we need to remove a bunch of entries of the payment frequencies | ||||
94 | d->ui->m_termUnit->clear(); | ||||
95 | | ||||
96 | d->m_mandatoryGroup->clear(); | ||||
97 | if (!d->m_wizard->openingBalance().isZero()) { | ||||
98 | d->m_mandatoryGroup->add(d->ui->m_loanAmount->lineedit()); | ||||
99 | if (d->ui->m_loanAmount->lineedit()->text().length() == 0) { | ||||
100 | d->ui->m_loanAmount->setValue(d->m_wizard->openingBalance().abs()); | ||||
101 | } | ||||
102 | } | ||||
103 | | ||||
104 | switch (d->m_wizard->d_func()->m_generalLoanInfoPage->d_func()->ui->m_paymentFrequency->currentItem()) { | ||||
105 | default: | ||||
106 | d->ui->m_termUnit->insertItem(i18n("Payments"), (int)Schedule::Occurrence::Once); | ||||
107 | d->ui->m_termUnit->setCurrentItem((int)Schedule::Occurrence::Once); | ||||
108 | break; | ||||
109 | case Schedule::Occurrence::Monthly: | ||||
110 | d->ui->m_termUnit->insertItem(i18n("Months"), (int)Schedule::Occurrence::Monthly); | ||||
111 | d->ui->m_termUnit->insertItem(i18n("Years"), (int)Schedule::Occurrence::Yearly); | ||||
112 | d->ui->m_termUnit->setCurrentItem((int)Schedule::Occurrence::Monthly); | ||||
113 | break; | ||||
114 | case Schedule::Occurrence::Yearly: | ||||
115 | d->ui->m_termUnit->insertItem(i18n("Years"), (int)Schedule::Occurrence::Yearly); | ||||
116 | d->ui->m_termUnit->setCurrentItem((int)Schedule::Occurrence::Yearly); | ||||
117 | break; | ||||
118 | } | ||||
119 | } | ||||
120 | | ||||
121 | void LoanDetailsPage::slotValuesChanged() | ||||
122 | { | ||||
123 | Q_D(LoanDetailsPage); | ||||
124 | d->m_needCalculate = true; | ||||
125 | d->m_wizard->completeStateChanged(); | ||||
126 | } | ||||
127 | | ||||
128 | void LoanDetailsPage::slotCalculate() | ||||
129 | { | ||||
130 | Q_D(LoanDetailsPage); | ||||
131 | MyMoneyFinancialCalculator calc; | ||||
132 | double val; | ||||
133 | int PF, CF; | ||||
134 | QString result; | ||||
135 | bool moneyBorrowed = d->m_wizard->moneyBorrowed(); | ||||
136 | bool moneyLend = !moneyBorrowed; | ||||
137 | | ||||
138 | // FIXME: for now, we only support interest calculation at the end of the period | ||||
139 | calc.setBep(); | ||||
140 | // FIXME: for now, we only support periodic compounding | ||||
141 | calc.setDisc(); | ||||
142 | | ||||
143 | PF = d->m_wizard->d_func()->m_generalLoanInfoPage->d_func()->ui->m_paymentFrequency->eventsPerYear(); | ||||
144 | CF = d->m_wizard->d_func()->m_generalLoanInfoPage->d_func()->ui->m_compoundFrequency->eventsPerYear(); | ||||
145 | | ||||
146 | if (PF == 0 || CF == 0) | ||||
147 | return; | ||||
148 | | ||||
149 | calc.setPF(PF); | ||||
150 | calc.setCF(CF); | ||||
151 | | ||||
152 | | ||||
153 | if (!d->ui->m_loanAmount->lineedit()->text().isEmpty()) { | ||||
154 | val = d->ui->m_loanAmount->value().abs().toDouble(); | ||||
155 | if (moneyBorrowed) | ||||
156 | val = -val; | ||||
157 | calc.setPv(val); | ||||
158 | } | ||||
159 | | ||||
160 | if (!d->ui->m_interestRate->lineedit()->text().isEmpty()) { | ||||
161 | val = d->ui->m_interestRate->value().abs().toDouble(); | ||||
162 | calc.setIr(val); | ||||
163 | } | ||||
164 | | ||||
165 | if (!d->ui->m_paymentAmount->lineedit()->text().isEmpty()) { | ||||
166 | val = d->ui->m_paymentAmount->value().abs().toDouble(); | ||||
167 | if (moneyLend) | ||||
168 | val = -val; | ||||
169 | calc.setPmt(val); | ||||
170 | } | ||||
171 | | ||||
172 | if (!d->ui->m_balloonAmount->lineedit()->text().isEmpty()) { | ||||
173 | val = d->ui->m_balloonAmount->value().abs().toDouble(); | ||||
174 | if (moneyLend) | ||||
175 | val = -val; | ||||
176 | calc.setFv(val); | ||||
177 | } | ||||
178 | | ||||
179 | if (d->ui->m_termAmount->value() != 0) { | ||||
180 | calc.setNpp(term()); | ||||
181 | } | ||||
182 | | ||||
183 | // setup of parameters is done, now do the calculation | ||||
184 | try { | ||||
185 | if (d->ui->m_loanAmount->lineedit()->text().isEmpty()) { | ||||
186 | // calculate the amount of the loan out of the other information | ||||
187 | val = calc.presentValue(); | ||||
188 | d->ui->m_loanAmount->loadText(MyMoneyMoney(static_cast<double>(val)).abs().formatMoney(QString(), d->m_wizard->d_func()->precision())); | ||||
189 | result = i18n("KMyMoney has calculated the amount of the loan as %1.", d->ui->m_loanAmount->lineedit()->text()); | ||||
190 | | ||||
191 | } else if (d->ui->m_interestRate->lineedit()->text().isEmpty()) { | ||||
192 | // calculate the interest rate out of the other information | ||||
193 | val = calc.interestRate(); | ||||
194 | | ||||
195 | d->ui->m_interestRate->loadText(MyMoneyMoney(static_cast<double>(val)).abs().formatMoney(QString(), 3)); | ||||
196 | result = i18n("KMyMoney has calculated the interest rate to %1%.", d->ui->m_interestRate->lineedit()->text()); | ||||
197 | | ||||
198 | } else if (d->ui->m_paymentAmount->lineedit()->text().isEmpty()) { | ||||
199 | // calculate the periodical amount of the payment out of the other information | ||||
200 | val = calc.payment(); | ||||
201 | d->ui->m_paymentAmount->setValue(MyMoneyMoney(static_cast<double>(val)).abs()); | ||||
202 | // reset payment as it might have changed due to rounding | ||||
203 | val = d->ui->m_paymentAmount->value().abs().toDouble(); | ||||
204 | if (moneyLend) | ||||
205 | val = -val; | ||||
206 | calc.setPmt(val); | ||||
207 | | ||||
208 | result = i18n("KMyMoney has calculated a periodic payment of %1 to cover principal and interest.", d->ui->m_paymentAmount->lineedit()->text()); | ||||
209 | | ||||
210 | val = calc.futureValue(); | ||||
211 | if ((moneyBorrowed && val < 0 && qAbs(val) >= qAbs(calc.payment())) | ||||
212 | || (moneyLend && val > 0 && qAbs(val) >= qAbs(calc.payment()))) { | ||||
213 | calc.setNpp(calc.npp() - 1); | ||||
214 | // updateTermWidgets(calc.npp()); | ||||
215 | val = calc.futureValue(); | ||||
216 | MyMoneyMoney refVal(static_cast<double>(val)); | ||||
217 | d->ui->m_balloonAmount->loadText(refVal.abs().formatMoney(QString(), d->m_wizard->d_func()->precision())); | ||||
218 | result += QString(" "); | ||||
219 | result += i18n("The number of payments has been decremented and the balloon payment has been modified to %1.", d->ui->m_balloonAmount->lineedit()->text()); | ||||
220 | } else if ((moneyBorrowed && val < 0 && qAbs(val) < qAbs(calc.payment())) | ||||
221 | || (moneyLend && val > 0 && qAbs(val) < qAbs(calc.payment()))) { | ||||
222 | d->ui->m_balloonAmount->loadText(MyMoneyMoney().formatMoney(QString(), d->m_wizard->d_func()->precision())); | ||||
223 | } else { | ||||
224 | MyMoneyMoney refVal(static_cast<double>(val)); | ||||
225 | d->ui->m_balloonAmount->loadText(refVal.abs().formatMoney(QString(), d->m_wizard->d_func()->precision())); | ||||
226 | result += i18n("The balloon payment has been modified to %1.", d->ui->m_balloonAmount->lineedit()->text()); | ||||
227 | } | ||||
228 | | ||||
229 | } else if (d->ui->m_termAmount->value() == 0) { | ||||
230 | // calculate the number of payments out of the other information | ||||
231 | val = calc.numPayments(); | ||||
232 | if (val == 0) | ||||
233 | throw MYMONEYEXCEPTION("incorrect fincancial calculation"); | ||||
234 | | ||||
235 | // if the number of payments has a fractional part, then we | ||||
236 | // round it to the smallest integer and calculate the balloon payment | ||||
237 | result = i18n("KMyMoney has calculated the term of your loan as %1. ", updateTermWidgets(qFloor(val))); | ||||
238 | | ||||
239 | if (val != qFloor(val)) { | ||||
240 | calc.setNpp(qFloor(val)); | ||||
241 | val = calc.futureValue(); | ||||
242 | MyMoneyMoney refVal(static_cast<double>(val)); | ||||
243 | d->ui->m_balloonAmount->loadText(refVal.abs().formatMoney(QString(), d->m_wizard->d_func()->precision())); | ||||
244 | result += i18n("The balloon payment has been modified to %1.", d->ui->m_balloonAmount->lineedit()->text()); | ||||
245 | } | ||||
246 | | ||||
247 | } else { | ||||
248 | // calculate the future value of the loan out of the other information | ||||
249 | val = calc.futureValue(); | ||||
250 | | ||||
251 | // we differentiate between the following cases: | ||||
252 | // a) the future value is greater than a payment | ||||
253 | // b) the future value is less than a payment or the loan is overpaid | ||||
254 | // c) all other cases | ||||
255 | // | ||||
256 | // a) means, we have paid more than we owed. This can't be | ||||
257 | // b) means, we paid more than we owed but the last payment is | ||||
258 | // less in value than regular payments. That means, that the | ||||
259 | // future value is to be treated as (fully payed back) | ||||
260 | // c) the loan is not payed back yet | ||||
261 | if ((moneyBorrowed && val < 0 && qAbs(val) > qAbs(calc.payment())) | ||||
262 | || (moneyLend && val > 0 && qAbs(val) > qAbs(calc.payment()))) { | ||||
263 | // case a) | ||||
264 | qDebug("Future Value is %f", val); | ||||
265 | throw MYMONEYEXCEPTION("incorrect fincancial calculation"); | ||||
266 | | ||||
267 | } else if ((moneyBorrowed && val < 0 && qAbs(val) <= qAbs(calc.payment())) | ||||
268 | || (moneyLend && val > 0 && qAbs(val) <= qAbs(calc.payment()))) { | ||||
269 | // case b) | ||||
270 | val = 0; | ||||
271 | } | ||||
272 | | ||||
273 | MyMoneyMoney refVal(static_cast<double>(val)); | ||||
274 | result = i18n("KMyMoney has calculated a balloon payment of %1 for this loan.", refVal.abs().formatMoney(QString(), d->m_wizard->d_func()->precision())); | ||||
275 | | ||||
276 | if (!d->ui->m_balloonAmount->lineedit()->text().isEmpty()) { | ||||
277 | if ((d->ui->m_balloonAmount->value().abs() - refVal.abs()).abs().toDouble() > 1) { | ||||
278 | throw MYMONEYEXCEPTION("incorrect fincancial calculation"); | ||||
279 | } | ||||
280 | result = i18n("KMyMoney has successfully verified your loan information."); | ||||
281 | } | ||||
282 | d->ui->m_balloonAmount->loadText(refVal.abs().formatMoney(QString(), d->m_wizard->d_func()->precision())); | ||||
283 | } | ||||
284 | | ||||
285 | } catch (const MyMoneyException &) { | ||||
286 | KMessageBox::error(0, | ||||
287 | i18n("You have entered mis-matching information. Please modify " | ||||
288 | "your figures or leave one value empty " | ||||
289 | "to let KMyMoney calculate it for you"), | ||||
290 | i18n("Calculation error")); | ||||
291 | return; | ||||
292 | } | ||||
293 | | ||||
294 | result += i18n("\n\nAccept this or modify the loan information and recalculate."); | ||||
295 | | ||||
296 | KMessageBox::information(0, result, i18n("Calculation successful")); | ||||
297 | d->m_needCalculate = false; | ||||
298 | | ||||
299 | // now update change | ||||
300 | d->m_wizard->completeStateChanged(); | ||||
301 | } | ||||
302 | | ||||
303 | int LoanDetailsPage::term() const | ||||
304 | { | ||||
305 | Q_D(const LoanDetailsPage); | ||||
306 | int factor = 0; | ||||
307 | | ||||
308 | if (d->ui->m_termAmount->value() != 0) { | ||||
309 | factor = 1; | ||||
310 | switch (d->ui->m_termUnit->currentItem()) { | ||||
311 | case Schedule::Occurrence::Yearly: // years | ||||
312 | factor = 12; | ||||
313 | // intentional fall through | ||||
314 | | ||||
315 | case Schedule::Occurrence::Monthly: // months | ||||
316 | factor *= 30; | ||||
317 | factor *= d->ui->m_termAmount->value(); | ||||
318 | // factor now is the duration in days. we divide this by the | ||||
319 | // payment frequency and get the number of payments | ||||
320 | factor /= d->m_wizard->d_func()->m_generalLoanInfoPage->d_func()->ui->m_paymentFrequency->daysBetweenEvents(); | ||||
321 | break; | ||||
322 | | ||||
323 | default: | ||||
324 | qDebug("Unknown term unit %d in LoanDetailsPage::term(). Using payments.", (int)d->ui->m_termUnit->currentItem()); | ||||
325 | // intentional fall through | ||||
326 | | ||||
327 | case Schedule::Occurrence::Once: // payments | ||||
328 | factor = d->ui->m_termAmount->value(); | ||||
329 | break; | ||||
330 | } | ||||
331 | } | ||||
332 | return factor; | ||||
333 | } | ||||
334 | | ||||
335 | QString LoanDetailsPage::updateTermWidgets(const double val) | ||||
336 | { | ||||
337 | Q_D(LoanDetailsPage); | ||||
338 | long vl = qFloor(val); | ||||
339 | | ||||
340 | QString valString; | ||||
341 | Schedule::Occurrence unit = d->ui->m_termUnit->currentItem(); | ||||
342 | | ||||
343 | if ((unit == Schedule::Occurrence::Monthly) | ||||
344 | && ((vl % 12) == 0)) { | ||||
345 | vl /= 12; | ||||
346 | unit = Schedule::Occurrence::Yearly; | ||||
347 | } | ||||
348 | | ||||
349 | switch (unit) { | ||||
350 | case Schedule::Occurrence::Monthly: | ||||
351 | valString = i18np("one month", "%1 months", vl); | ||||
352 | d->ui->m_termUnit->setCurrentItem((int)Schedule::Occurrence::Monthly); | ||||
353 | break; | ||||
354 | case Schedule::Occurrence::Yearly: | ||||
355 | valString = i18np("one year", "%1 years", vl); | ||||
356 | d->ui->m_termUnit->setCurrentItem((int)Schedule::Occurrence::Yearly); | ||||
357 | break; | ||||
358 | default: | ||||
359 | valString = i18np("one payment", "%1 payments", vl); | ||||
360 | d->ui->m_termUnit->setCurrentItem((int)Schedule::Occurrence::Once); | ||||
361 | break; | ||||
362 | } | ||||
363 | d->ui->m_termAmount->setValue(vl); | ||||
364 | return valString; | ||||
365 | } | ||||
366 | | ||||
367 | bool LoanDetailsPage::isComplete() const | ||||
368 | { | ||||
369 | Q_D(const LoanDetailsPage); | ||||
370 | // bool rc = KMyMoneyWizardPage::isComplete(); | ||||
371 | | ||||
372 | int fieldCnt = 0; | ||||
373 | | ||||
374 | if (d->ui->m_loanAmount->lineedit()->text().length() > 0) { | ||||
375 | fieldCnt++; | ||||
376 | } | ||||
377 | | ||||
378 | if (d->ui->m_interestRate->lineedit()->text().length() > 0) { | ||||
379 | fieldCnt++; | ||||
380 | } | ||||
381 | | ||||
382 | if (d->ui->m_termAmount->value() != 0) { | ||||
383 | fieldCnt++; | ||||
384 | } | ||||
385 | | ||||
386 | if (d->ui->m_paymentAmount->lineedit()->text().length() > 0) { | ||||
387 | fieldCnt++; | ||||
388 | } | ||||
389 | | ||||
390 | if (d->ui->m_balloonAmount->lineedit()->text().length() > 0) { | ||||
391 | fieldCnt++; | ||||
392 | } | ||||
393 | | ||||
394 | d->ui->m_calculateButton->setEnabled(fieldCnt == 4 || (fieldCnt == 5 && d->m_needCalculate)); | ||||
395 | | ||||
396 | d->ui->m_calculateButton->setAutoDefault(false); | ||||
397 | d->ui->m_calculateButton->setDefault(false); | ||||
398 | if (d->m_needCalculate && fieldCnt == 4) { | ||||
399 | d->m_wizard->d_func()->m_nextButton->setToolTip(i18n("Press Calculate to verify the values")); | ||||
400 | d->ui->m_calculateButton->setAutoDefault(true); | ||||
401 | d->ui->m_calculateButton->setDefault(true); | ||||
402 | } else if (fieldCnt != 5) { | ||||
403 | d->m_wizard->d_func()->m_nextButton->setToolTip(i18n("Not all details supplied")); | ||||
404 | d->ui->m_calculateButton->setAutoDefault(true); | ||||
405 | d->ui->m_calculateButton->setDefault(true); | ||||
406 | } | ||||
407 | d->m_wizard->d_func()->m_nextButton->setAutoDefault(!d->ui->m_calculateButton->autoDefault()); | ||||
408 | d->m_wizard->d_func()->m_nextButton->setDefault(!d->ui->m_calculateButton->autoDefault()); | ||||
409 | | ||||
410 | return (fieldCnt == 5) && !d->m_needCalculate; | ||||
411 | } | ||||
412 | | ||||
413 | QWidget* LoanDetailsPage::initialFocusWidget(void) const | ||||
414 | { | ||||
415 | Q_D(const LoanDetailsPage); | ||||
416 | return d->ui->m_paymentDue; | ||||
417 | } | ||||
418 | | ||||
419 | KMyMoneyWizardPage* LoanDetailsPage::nextPage() const | ||||
420 | { | ||||
421 | Q_D(const LoanDetailsPage); | ||||
422 | return d->m_wizard->d_func()->m_loanPaymentPage; | ||||
423 | } | ||||
424 | } |