File indexing completed on 2024-06-23 05:02:59
0001 /* 0002 SPDX-FileCopyrightText: 2017 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> 0003 SPDX-License-Identifier: GPL-2.0-or-later 0004 */ 0005 0006 #ifndef KNEWLOANWIZARD_P_H 0007 #define KNEWLOANWIZARD_P_H 0008 0009 #include "knewloanwizard.h" 0010 0011 // ---------------------------------------------------------------------------- 0012 // QT Includes 0013 0014 #include <QBitArray> 0015 #include <qmath.h> 0016 0017 // ---------------------------------------------------------------------------- 0018 // KDE Includes 0019 0020 #include <KLocalizedString> 0021 #include <KMessageBox> 0022 0023 // ---------------------------------------------------------------------------- 0024 // Project Includes 0025 0026 #include "ui_knewloanwizard.h" 0027 #include "ui_namewizardpage.h" 0028 #include "ui_firstpaymentwizardpage.h" 0029 #include "ui_loanamountwizardpage.h" 0030 #include "ui_interestwizardpage.h" 0031 #include "ui_paymenteditwizardpage.h" 0032 #include "ui_finalpaymentwizardpage.h" 0033 #include "ui_interestcategorywizardpage.h" 0034 #include "ui_assetaccountwizardpage.h" 0035 #include "ui_schedulewizardpage.h" 0036 #include "ui_paymentwizardpage.h" 0037 0038 #include "kmymoneysettings.h" 0039 #include "kmymoneyutils.h" 0040 #include "mymoneyaccountloan.h" 0041 #include "mymoneyenums.h" 0042 #include "mymoneyexception.h" 0043 #include "mymoneyfile.h" 0044 #include "mymoneyfinancialcalculator.h" 0045 #include "mymoneyschedule.h" 0046 #include "mymoneysecurity.h" 0047 #include "mymoneysplit.h" 0048 #include "mymoneytransaction.h" 0049 #include "splitmodel.h" 0050 0051 namespace Ui { 0052 class KNewLoanWizard; 0053 } 0054 0055 class KNewLoanWizard; 0056 class KNewLoanWizardPrivate 0057 { 0058 Q_DISABLE_COPY(KNewLoanWizardPrivate) 0059 Q_DECLARE_PUBLIC(KNewLoanWizard) 0060 0061 public: 0062 explicit KNewLoanWizardPrivate(KNewLoanWizard *qq) : 0063 q_ptr(qq), 0064 ui(new Ui::KNewLoanWizard) 0065 { 0066 } 0067 0068 ~KNewLoanWizardPrivate() 0069 { 0070 delete ui; 0071 } 0072 0073 void init() 0074 { 0075 Q_Q(KNewLoanWizard); 0076 ui->setupUi(q); 0077 m_pages = QBitArray(KNewLoanWizard::Page_Summary + 1, true); 0078 q->setModal(true); 0079 0080 KMyMoneyMVCCombo::setSubstringSearchForChildren(ui->m_namePage, !KMyMoneySettings::stringMatchFromStart()); 0081 0082 // make sure, the back button does not clear fields 0083 q->setOption(QWizard::IndependentPages, true); 0084 0085 // connect(m_payeeEdit, SIGNAL(newPayee(QString)), this, SLOT(slotNewPayee(QString))); 0086 q->connect(ui->m_namePage->ui->m_payeeEdit, &KMyMoneyMVCCombo::createItem, q, &KNewLoanWizard::slotNewPayee); 0087 0088 q->connect(ui->m_additionalFeesPage, &AdditionalFeesWizardPage::newCategory, q, &KNewLoanWizard::slotNewCategory); 0089 0090 q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KNewLoanWizard::slotReloadEditWidgets); 0091 0092 resetCalculator(); 0093 0094 q->slotReloadEditWidgets(); 0095 0096 // As default we assume a liability loan, with fixed interest rate, 0097 // with a first payment due on the 30th of this month. All payments 0098 // should be recorded and none have been made so far. 0099 0100 //FIXME: port 0101 ui->m_firstPaymentPage->ui->m_firstDueDateEdit->setDate(QDate(QDate::currentDate().year(), QDate::currentDate().month(), 30)); 0102 0103 // FIXME: we currently only support interest calculation on reception 0104 m_pages.clearBit(KNewLoanWizard::Page_InterestCalculation); 0105 0106 // turn off all pages that are contained here for derived classes 0107 m_pages.clearBit(KNewLoanWizard::Page_EditIntro); 0108 m_pages.clearBit(KNewLoanWizard::Page_EditSelection); 0109 m_pages.clearBit(KNewLoanWizard::Page_EffectiveDate); 0110 m_pages.clearBit(KNewLoanWizard::Page_PaymentEdit); 0111 m_pages.clearBit(KNewLoanWizard::Page_InterestEdit); 0112 m_pages.clearBit(KNewLoanWizard::Page_SummaryEdit); 0113 0114 // for now, we don't have online help :-( 0115 q->setOption(QWizard::HaveHelpButton, false); 0116 0117 // setup a phony transaction for additional fee processing 0118 m_account = MyMoneyAccount("Phony-ID", MyMoneyAccount()); 0119 m_phonySplit.setAccountId(m_account.id()); 0120 m_phonySplit.setValue(MyMoneyMoney()); 0121 m_additionalFeesTransaction.addSplit(m_phonySplit); 0122 0123 KMyMoneyUtils::updateWizardButtons(q); 0124 } 0125 0126 void resetCalculator() 0127 { 0128 Q_Q(KNewLoanWizard); 0129 ui->m_loanAmountPage->resetCalculator(); 0130 ui->m_interestPage->resetCalculator(); 0131 ui->m_durationPage->resetCalculator(); 0132 ui->m_paymentPage->resetCalculator(); 0133 ui->m_finalPaymentPage->resetCalculator(); 0134 0135 q->setField("additionalCost", MyMoneyMoney().formatMoney(m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId())))); 0136 } 0137 0138 void updateLoanAmount() 0139 { 0140 Q_Q(KNewLoanWizard); 0141 QString txt; 0142 //FIXME: port 0143 if (! q->field("loanAmountEditValid").toBool()) { 0144 txt = QString("<") + i18n("calculate") + QString(">"); 0145 } else { 0146 txt = q->field("loanAmountEdit").value<MyMoneyMoney>().formatMoney(m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId()))); 0147 } 0148 q->setField("loanAmount1", txt); 0149 q->setField("loanAmount2", txt); 0150 q->setField("loanAmount3", txt); 0151 q->setField("loanAmount4", txt); 0152 q->setField("loanAmount5", txt); 0153 } 0154 0155 void updateInterestRate() 0156 { 0157 Q_Q(KNewLoanWizard); 0158 QString txt; 0159 //FIXME: port 0160 if (! q->field("interestRateEditValid").toBool()) { 0161 txt = QString("<") + i18n("calculate") + QString(">"); 0162 } else { 0163 txt = q->field("interestRateEdit").value<MyMoneyMoney>().formatMoney(QString(), 3) + QString("%"); 0164 } 0165 q->setField("interestRate1", txt); 0166 q->setField("interestRate2", txt); 0167 q->setField("interestRate3", txt); 0168 q->setField("interestRate4", txt); 0169 q->setField("interestRate5", txt); 0170 } 0171 0172 void updateDuration() 0173 { 0174 Q_Q(KNewLoanWizard); 0175 QString txt; 0176 //FIXME: port 0177 if (q->field("durationValueEdit").toInt() == 0) { 0178 txt = QString("<") + i18n("calculate") + QString(">"); 0179 } else { 0180 txt = QStringLiteral("%1 %2").arg(q->field("durationValueEdit").toInt()).arg(q->field("durationUnitEdit").toString()); 0181 } 0182 q->setField("duration1", txt); 0183 q->setField("duration2", txt); 0184 q->setField("duration3", txt); 0185 q->setField("duration4", txt); 0186 q->setField("duration5", txt); 0187 } 0188 0189 void updatePayment() 0190 { 0191 Q_Q(KNewLoanWizard); 0192 QString txt; 0193 //FIXME: port 0194 if (! q->field("paymentEditValid").toBool()) { 0195 txt = QString("<") + i18n("calculate") + QString(">"); 0196 } else { 0197 txt = q->field("paymentEdit").value<MyMoneyMoney>().formatMoney(m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId()))); 0198 } 0199 q->setField("payment1", txt); 0200 q->setField("payment2", txt); 0201 q->setField("payment3", txt); 0202 q->setField("payment4", txt); 0203 q->setField("payment5", txt); 0204 q->setField("basePayment", txt); 0205 } 0206 0207 void updateFinalPayment() 0208 { 0209 Q_Q(KNewLoanWizard); 0210 QString txt; 0211 //FIXME: port 0212 if (! q->field("finalPaymentEditValid").toBool()) { 0213 txt = QString("<") + i18n("calculate") + QString(">"); 0214 } else { 0215 txt = q->field("finalPaymentEdit").value<MyMoneyMoney>().formatMoney(m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId()))); 0216 } 0217 q->setField("balloon1", txt); 0218 q->setField("balloon2", txt); 0219 q->setField("balloon3", txt); 0220 q->setField("balloon4", txt); 0221 q->setField("balloon5", txt); 0222 } 0223 0224 void updateLoanInfo() 0225 { 0226 Q_Q(KNewLoanWizard); 0227 updateLoanAmount(); 0228 updateInterestRate(); 0229 updateDuration(); 0230 updatePayment(); 0231 updateFinalPayment(); 0232 ui->m_additionalFeesPage->updatePeriodicPayment(m_account); 0233 0234 QString txt; 0235 0236 int fraction = m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId())); 0237 q->setField("loanAmount6", q->field("loanAmountEdit").value<MyMoneyMoney>().formatMoney(fraction)); 0238 q->setField("interestRate6", QString(q->field("interestRateEdit").value<MyMoneyMoney>().formatMoney("", 3) + QString("%"))); 0239 txt = QStringLiteral("%1 %2").arg(q->field("durationValueEdit").toInt()).arg(q->field("durationUnitEdit").toString()); 0240 q->setField("duration6", txt); 0241 q->setField("payment6", q->field("paymentEdit").value<MyMoneyMoney>().formatMoney(fraction)); 0242 q->setField("balloon6", q->field("finalPaymentEdit").value<MyMoneyMoney>().formatMoney(fraction)); 0243 } 0244 0245 int calculateLoan() 0246 { 0247 Q_Q(KNewLoanWizard); 0248 MyMoneyFinancialCalculator calc; 0249 double val; 0250 int PF; 0251 QString result; 0252 0253 // FIXME: for now, we only support interest calculation at the end of the period 0254 calc.setBep(); 0255 // FIXME: for now, we only support periodic compounding 0256 calc.setDisc(); 0257 0258 PF = MyMoneySchedule::eventsPerYear(eMyMoney::Schedule::Occurrence(q->field("paymentFrequencyUnitEdit").toInt())); 0259 if (PF == 0) 0260 return 0; 0261 calc.setPF(PF); 0262 0263 // FIXME: for now we only support compounding frequency == payment frequency 0264 calc.setCF(PF); 0265 0266 if (q->field("loanAmountEditValid").toBool()) { 0267 val = q->field("loanAmountEdit").value<MyMoneyMoney>().abs().toDouble(); 0268 if (q->field("borrowButton").toBool()) 0269 val = -val; 0270 calc.setPv(val); 0271 } 0272 0273 if (q->field("interestRateEditValid").toBool()) { 0274 val = q->field("interestRateEdit").value<MyMoneyMoney>().abs().toDouble(); 0275 calc.setIr(val); 0276 } 0277 0278 if (q->field("paymentEditValid").toBool()) { 0279 val = q->field("paymentEdit").value<MyMoneyMoney>().abs().toDouble(); 0280 if (q->field("lendButton").toBool()) 0281 val = -val; 0282 calc.setPmt(val); 0283 } 0284 0285 if (q->field("finalPaymentEditValid").toBool()) { 0286 val = q->field("finalPaymentEditValid").value<MyMoneyMoney>().abs().toDouble(); 0287 if (q->field("lendButton").toBool()) 0288 val = -val; 0289 calc.setFv(val); 0290 } 0291 0292 if (q->field("durationValueEdit").toInt() != 0) { 0293 calc.setNpp(ui->m_durationPage->term()); 0294 } 0295 0296 int fraction = m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId())); 0297 // setup of parameters is done, now do the calculation 0298 try { 0299 //FIXME: port 0300 if (!q->field("loanAmountEditValid").toBool()) { 0301 // calculate the amount of the loan out of the other information 0302 val = calc.presentValue(); 0303 ui->m_loanAmountPage->ui->m_loanAmountEdit->setText(MyMoneyMoney(static_cast<double>(val)).abs().formatMoney(fraction)); 0304 result = i18n("KMyMoney has calculated the amount of the loan as %1.", ui->m_loanAmountPage->ui->m_loanAmountEdit->text()); 0305 0306 } else if (!q->field("interestRateEditValid").toBool()) { 0307 // calculate the interest rate out of the other information 0308 val = calc.interestRate(); 0309 0310 ui->m_interestPage->ui->m_interestRateEdit->setText(MyMoneyMoney(static_cast<double>(val)).abs().formatMoney("", 3)); 0311 result = i18n("KMyMoney has calculated the interest rate to %1%.", ui->m_interestPage->ui->m_interestRateEdit->text()); 0312 0313 } else if (!q->field("paymentEditValid").toBool()) { 0314 // calculate the periodical amount of the payment out of the other information 0315 val = calc.payment(); 0316 q->setField("paymentEdit", QVariant::fromValue<MyMoneyMoney>(MyMoneyMoney(val).abs())); 0317 // reset payment as it might have changed due to rounding 0318 val = q->field("paymentEdit").value<MyMoneyMoney>().abs().toDouble(); 0319 if (q->field("lendButton").toBool()) 0320 val = -val; 0321 calc.setPmt(val); 0322 0323 result = i18n("KMyMoney has calculated a periodic payment of %1 to cover principal and interest.", ui->m_paymentPage->ui->m_paymentEdit->text()); 0324 0325 val = calc.futureValue(); 0326 if ((q->field("borrowButton").toBool() && val < 0 && qAbs(val) >= qAbs(calc.payment())) 0327 || (q->field("lendButton").toBool() && val > 0 && qAbs(val) >= qAbs(calc.payment()))) { 0328 calc.setNpp(calc.npp() - 1); 0329 ui->m_durationPage->updateTermWidgets(calc.npp()); 0330 val = calc.futureValue(); 0331 MyMoneyMoney refVal(static_cast<double>(val)); 0332 ui->m_finalPaymentPage->ui->m_finalPaymentEdit->setText(refVal.abs().formatMoney(fraction)); 0333 result += QString(" "); 0334 result += i18n("The number of payments has been decremented and the final payment has been modified to %1.", ui->m_finalPaymentPage->ui->m_finalPaymentEdit->text()); 0335 } else if ((q->field("borrowButton").toBool() && val < 0 && qAbs(val) < qAbs(calc.payment())) 0336 || (q->field("lendButton").toBool() && val > 0 && qAbs(val) < qAbs(calc.payment()))) { 0337 ui->m_finalPaymentPage->ui->m_finalPaymentEdit->setText(MyMoneyMoney().formatMoney(fraction)); 0338 } else { 0339 MyMoneyMoney refVal(static_cast<double>(val)); 0340 ui->m_finalPaymentPage->ui->m_finalPaymentEdit->setText(refVal.abs().formatMoney(fraction)); 0341 result += i18n("The final payment has been modified to %1.", ui->m_finalPaymentPage->ui->m_finalPaymentEdit->text()); 0342 } 0343 0344 } else if (q->field("durationValueEdit").toInt() == 0) { 0345 // calculate the number of payments out of the other information 0346 val = calc.numPayments(); 0347 if (val == 0) 0348 throw MYMONEYEXCEPTION_CSTRING("incorrect financial calculation"); 0349 0350 // if the number of payments has a fractional part, then we 0351 // round it to the smallest integer and calculate the balloon payment 0352 result = i18n("KMyMoney has calculated the term of your loan as %1. ", ui->m_durationPage->updateTermWidgets(qFloor(val))); 0353 0354 if (val != qFloor(val)) { 0355 calc.setNpp(qFloor(val)); 0356 val = calc.futureValue(); 0357 MyMoneyMoney refVal(static_cast<double>(val)); 0358 ui->m_finalPaymentPage->ui->m_finalPaymentEdit->setText(refVal.abs().formatMoney(fraction)); 0359 result += i18n("The final payment has been modified to %1.", ui->m_finalPaymentPage->ui->m_finalPaymentEdit->text()); 0360 } 0361 0362 } else { 0363 // calculate the future value of the loan out of the other information 0364 val = calc.futureValue(); 0365 0366 // we differentiate between the following cases: 0367 // a) the future value is greater than a payment 0368 // b) the future value is less than a payment or the loan is overpaid 0369 // c) all other cases 0370 // 0371 // a) means, we have paid more than we owed. This can't be 0372 // b) means, we paid more than we owed but the last payment is 0373 // less in value than regular payments. That means, that the 0374 // future value is to be treated as (fully paid back) 0375 // c) the loan is not paid back yet 0376 0377 if ((q->field("borrowButton").toBool() && val < 0 && qAbs(val) > qAbs(calc.payment())) 0378 || (q->field("lendButton").toBool() && val > 0 && qAbs(val) > qAbs(calc.payment()))) { 0379 // case a) 0380 qDebug("Future Value is %f", val); 0381 throw MYMONEYEXCEPTION_CSTRING("incorrect financial calculation"); 0382 0383 } else if ((q->field("borrowButton").toBool() && val < 0 && qAbs(val) <= qAbs(calc.payment())) 0384 || (q->field("lendButton").toBool() && val > 0 && qAbs(val) <= qAbs(calc.payment()))) { 0385 // case b) 0386 val = 0; 0387 } 0388 0389 MyMoneyMoney refVal(static_cast<double>(val)); 0390 result = i18n("KMyMoney has calculated a final payment of %1 for this loan.", refVal.abs().formatMoney(fraction)); 0391 0392 if (q->field("finalPaymentEditValid").toBool()) { 0393 if ((q->field("finalPaymentEdit").value<MyMoneyMoney>().abs() - refVal.abs()).abs().toDouble() > 1) { 0394 throw MYMONEYEXCEPTION_CSTRING("incorrect financial calculation"); 0395 } 0396 result = i18n("KMyMoney has successfully verified your loan information."); 0397 } 0398 //FIXME: port 0399 ui->m_finalPaymentPage->ui->m_finalPaymentEdit->setText(refVal.abs().formatMoney(fraction)); 0400 } 0401 0402 } catch (const MyMoneyException &) { 0403 KMessageBox::error(0, 0404 i18n("You have entered mis-matching information. Please backup to the " 0405 "appropriate page and update your figures or leave one value empty " 0406 "to let KMyMoney calculate it for you"), 0407 i18n("Calculation error")); 0408 return 0; 0409 } 0410 0411 result += i18n("\n\nAccept this or modify the loan information and recalculate."); 0412 0413 KMessageBox::information(0, result, i18n("Calculation successful")); 0414 return 1; 0415 } 0416 0417 /** 0418 * This method returns the transaction that is stored within 0419 * the schedule. See schedule(). 0420 * 0421 * @return MyMoneyTransaction object to be used within the schedule 0422 */ 0423 MyMoneyTransaction transaction() const 0424 { 0425 Q_Q(const KNewLoanWizard); 0426 MyMoneyTransaction t; 0427 bool hasInterest = !q->field("interestRateEdit").value<MyMoneyMoney>().isZero(); 0428 0429 MyMoneySplit sPayment, sInterest, sAmortization; 0430 // setup accounts. at this point, we cannot fill in the id of the 0431 // account that the amortization will be performed on, because we 0432 // create the account. So the id is yet unknown. But all others 0433 // must exist, otherwise we cannot create a schedule. 0434 sPayment.setAccountId(q->field("paymentAccountEdit").toStringList().first()); 0435 if (!sPayment.accountId().isEmpty()) { 0436 0437 //Only create the interest split if not zero 0438 if (hasInterest) { 0439 sInterest.setAccountId(q->field("interestAccountEdit").toStringList().first()); 0440 sInterest.setValue(MyMoneyMoney::autoCalc); 0441 sInterest.setShares(sInterest.value()); 0442 sInterest.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)); 0443 } 0444 0445 // values 0446 if (q->field("borrowButton").toBool()) { 0447 sPayment.setValue(-q->field("paymentEdit").value<MyMoneyMoney>()); 0448 } else { 0449 sPayment.setValue(q->field("paymentEdit").value<MyMoneyMoney>()); 0450 } 0451 0452 sAmortization.setValue(MyMoneyMoney::autoCalc); 0453 // don't forget the shares 0454 sPayment.setShares(sPayment.value()); 0455 0456 sAmortization.setShares(sAmortization.value()); 0457 0458 // setup the commodity 0459 MyMoneyAccount acc = MyMoneyFile::instance()->account(sPayment.accountId()); 0460 t.setCommodity(acc.currencyId()); 0461 0462 // actions 0463 sPayment.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization)); 0464 sAmortization.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization)); 0465 0466 // payee 0467 QString payeeId = q->field("payeeEdit").toString(); 0468 sPayment.setPayeeId(payeeId); 0469 sAmortization.setPayeeId(payeeId); 0470 0471 sAmortization.setAccountId(QStringLiteral("Phony-ID")); 0472 0473 // IMPORTANT: Payment split must be the first one, because 0474 // the schedule view expects it this way during display 0475 t.addSplit(sPayment); 0476 t.addSplit(sAmortization); 0477 0478 if (hasInterest) { 0479 t.addSplit(sInterest); 0480 } 0481 0482 // copy the splits from the other costs and update the payment split 0483 m_feeSplitModel.addSplitsToTransaction(t); 0484 0485 // and subtract the sum from the payment value 0486 sPayment.setValue(sPayment.value() - m_feeSplitModel.valueSum()); 0487 sPayment.setShares(sPayment.value()); 0488 t.modifySplit(sPayment); 0489 } 0490 return t; 0491 } 0492 0493 void loadAccountList() 0494 { 0495 Q_Q(KNewLoanWizard); 0496 AccountSet interestSet, assetSet; 0497 0498 if (q->field("borrowButton").toBool()) { 0499 interestSet.addAccountType(eMyMoney::Account::Type::Expense); 0500 } else { 0501 interestSet.addAccountType(eMyMoney::Account::Type::Income); 0502 } 0503 if (ui->m_interestCategoryPage) 0504 interestSet.load(ui->m_interestCategoryPage->ui->m_interestAccountEdit); 0505 0506 assetSet.addAccountType(eMyMoney::Account::Type::Checkings); 0507 assetSet.addAccountType(eMyMoney::Account::Type::Savings); 0508 assetSet.addAccountType(eMyMoney::Account::Type::Cash); 0509 assetSet.addAccountType(eMyMoney::Account::Type::Asset); 0510 assetSet.addAccountType(eMyMoney::Account::Type::Currency); 0511 if (ui->m_assetAccountPage) 0512 assetSet.load(ui->m_assetAccountPage->ui->m_assetAccountEdit); 0513 0514 assetSet.addAccountType(eMyMoney::Account::Type::CreditCard); 0515 assetSet.addAccountType(eMyMoney::Account::Type::Liability); 0516 if (ui->m_schedulePage) 0517 assetSet.load(ui->m_schedulePage->ui->m_paymentAccountEdit); 0518 } 0519 0520 KNewLoanWizard* q_ptr; 0521 Ui::KNewLoanWizard* ui; 0522 SplitModel m_feeSplitModel; 0523 MyMoneyAccountLoan m_account; 0524 MyMoneyTransaction m_additionalFeesTransaction; 0525 MyMoneySplit m_phonySplit; 0526 QBitArray m_pages; 0527 }; 0528 0529 #endif