void MyMoneyForecast::calculateAutoLoan(const MyMoneySchedule& schedule, MyMoneyTransaction& transaction, const QMap<QString, MyMoneyMoney>& balances) { if (schedule.type() == MyMoneySchedule::TYPE_LOANPAYMENT) { //get amortization and interest autoCalc splits MyMoneySplit amortizationSplit = transaction.amortizationSplit(); MyMoneySplit interestSplit = transaction.interestSplit(); if(!amortizationSplit.id().isEmpty() && !interestSplit.id().isEmpty()) { MyMoneyAccountLoan acc(MyMoneyFile::instance()->account(amortizationSplit.accountId())); MyMoneyFinancialCalculator calc; QDate dueDate; // FIXME: setup dueDate according to when the interest should be calculated // current implementation: take the date of the next payment according to // the schedule. If the calculation is based on the payment reception, and // the payment is overdue then take the current date dueDate = schedule.nextDueDate(); if(acc.interestCalculation() == MyMoneyAccountLoan::paymentReceived) { if(dueDate < QDate::currentDate()) dueDate = QDate::currentDate(); } // we need to calculate the balance at the time the payment is due MyMoneyMoney balance; if(balances.count() == 0) balance = MyMoneyFile::instance()->balance(acc.id(), dueDate.addDays(-1)); else balance = balances[acc.id()]; /* QValueList<MyMoneyTransaction> list; QValueList<MyMoneyTransaction>::ConstIterator it; MyMoneySplit split; MyMoneyTransactionFilter filter(acc.id()); filter.setDateFilter(QDate(), dueDate.addDays(-1)); list = MyMoneyFile::instance()->transactionList(filter); for(it = list.begin(); it != list.end(); ++it) { try { split = (*it).splitByAccount(acc.id()); balance += split.value(); } catch(MyMoneyException *e) { // account is not referenced within this transaction delete e; } } */ // FIXME: for now, we only support interest calculation at the end of the period calc.setBep(); // FIXME: for now, we only support periodic compounding calc.setDisc(); calc.setPF(MyMoneySchedule::eventsPerYear(schedule.occurence())); MyMoneySchedule::occurenceE compoundingOccurence = static_cast<MyMoneySchedule::occurenceE>(acc.interestCompounding()); if(compoundingOccurence == MyMoneySchedule::OCCUR_ANY) compoundingOccurence = schedule.occurence(); calc.setCF(MyMoneySchedule::eventsPerYear(compoundingOccurence)); calc.setPv(balance.toDouble()); calc.setIr(static_cast<FCALC_DOUBLE> (acc.interestRate(dueDate).abs().toDouble())); calc.setPmt(acc.periodicPayment().toDouble()); MyMoneyMoney interest(calc.interestDue()), amortization; interest = interest.abs(); // make sure it's positive for now amortization = acc.periodicPayment() - interest; if(acc.accountType() == MyMoneyAccount::AssetLoan) { interest = -interest; amortization = -amortization; } amortizationSplit.setShares(amortization); interestSplit.setShares(interest); // FIXME: for now we only assume loans to be in the currency of the transaction amortizationSplit.setValue(amortization); interestSplit.setValue(interest); transaction.modifySplit(amortizationSplit); transaction.modifySplit(interestSplit); } } }
int KNewLoanWizard::calculateLoan() { MyMoneyFinancialCalculator calc; double val; int PF; QString result; // FIXME: for now, we only support interest calculation at the end of the period calc.setBep(); // FIXME: for now, we only support periodic compounding calc.setDisc(); PF = MyMoneySchedule::eventsPerYear(MyMoneySchedule::occurrenceE(field("paymentFrequencyUnitEdit").toInt())); if (PF == 0) return 0; calc.setPF(PF); // FIXME: for now we only support compounding frequency == payment frequency calc.setCF(PF); if (field("loanAmountEditValid").toBool()) { val = field("loanAmountEdit").value<MyMoneyMoney>().abs().toDouble(); if (field("borrowButton").toBool()) val = -val; calc.setPv(val); } if (field("interestRateEditValid").toBool()) { val = field("interestRateEdit").value<MyMoneyMoney>().abs().toDouble(); calc.setIr(val); } if (field("paymentEditValid").toBool()) { val = field("paymentEdit").value<MyMoneyMoney>().abs().toDouble(); if (field("lendButton").toBool()) val = -val; calc.setPmt(val); } if (field("finalPaymentEditValid").toBool()) { val = field("finalPaymentEditValid").value<MyMoneyMoney>().abs().toDouble(); if (field("lendButton").toBool()) val = -val; calc.setFv(val); } if (field("durationValueEdit").toInt() != 0) { calc.setNpp(m_durationPage->term()); } int fraction = m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId())); // setup of parameters is done, now do the calculation try { //FIXME: port if (!field("loanAmountEditValid").toBool()) { // calculate the amount of the loan out of the other information val = calc.presentValue(); m_loanAmountPage->m_loanAmountEdit->loadText(MyMoneyMoney(static_cast<double>(val)).abs().formatMoney(fraction)); result = i18n("KMyMoney has calculated the amount of the loan as %1.", m_loanAmountPage->m_loanAmountEdit->lineedit()->text()); } else if (!field("interestRateEditValid").toBool()) { // calculate the interest rate out of the other information val = calc.interestRate(); m_interestPage->m_interestRateEdit->loadText(MyMoneyMoney(static_cast<double>(val)).abs().formatMoney("", 3)); result = i18n("KMyMoney has calculated the interest rate to %1%.", m_interestPage->m_interestRateEdit->lineedit()->text()); } else if (!field("paymentEditValid").toBool()) { // calculate the periodical amount of the payment out of the other information val = calc.payment(); setField("paymentEdit", QVariant::fromValue<MyMoneyMoney>(MyMoneyMoney(val).abs())); // reset payment as it might have changed due to rounding val = field("paymentEdit").value<MyMoneyMoney>().abs().toDouble(); if (field("lendButton").toBool()) val = -val; calc.setPmt(val); result = i18n("KMyMoney has calculated a periodic payment of %1 to cover principal and interest.", m_paymentPage->m_paymentEdit->lineedit()->text()); val = calc.futureValue(); if ((field("borrowButton").toBool() && val < 0 && qAbs(val) >= qAbs(calc.payment())) || (field("lendButton").toBool() && val > 0 && qAbs(val) >= qAbs(calc.payment()))) { calc.setNpp(calc.npp() - 1); m_durationPage->updateTermWidgets(calc.npp()); val = calc.futureValue(); MyMoneyMoney refVal(static_cast<double>(val)); m_finalPaymentPage->m_finalPaymentEdit->loadText(refVal.abs().formatMoney(fraction)); result += QString(" "); result += i18n("The number of payments has been decremented and the final payment has been modified to %1.", m_finalPaymentPage->m_finalPaymentEdit->lineedit()->text()); } else if ((field("borrowButton").toBool() && val < 0 && qAbs(val) < qAbs(calc.payment())) || (field("lendButton").toBool() && val > 0 && qAbs(val) < qAbs(calc.payment()))) { m_finalPaymentPage->m_finalPaymentEdit->loadText(MyMoneyMoney().formatMoney(fraction)); } else { MyMoneyMoney refVal(static_cast<double>(val)); m_finalPaymentPage->m_finalPaymentEdit->loadText(refVal.abs().formatMoney(fraction)); result += i18n("The final payment has been modified to %1.", m_finalPaymentPage->m_finalPaymentEdit->lineedit()->text()); } } else if (field("durationValueEdit").toInt() == 0) { // calculate the number of payments out of the other information val = calc.numPayments(); if (val == 0) throw MYMONEYEXCEPTION("incorrect fincancial calculation"); // if the number of payments has a fractional part, then we // round it to the smallest integer and calculate the balloon payment result = i18n("KMyMoney has calculated the term of your loan as %1. ", m_durationPage->updateTermWidgets(qFloor(val))); if (val != qFloor(val)) { calc.setNpp(qFloor(val)); val = calc.futureValue(); MyMoneyMoney refVal(static_cast<double>(val)); m_finalPaymentPage->m_finalPaymentEdit->loadText(refVal.abs().formatMoney(fraction)); result += i18n("The final payment has been modified to %1.", m_finalPaymentPage->m_finalPaymentEdit->lineedit()->text()); } } else { // calculate the future value of the loan out of the other information val = calc.futureValue(); // we differentiate between the following cases: // a) the future value is greater than a payment // b) the future value is less than a payment or the loan is overpaid // c) all other cases // // a) means, we have paid more than we owed. This can't be // b) means, we paid more than we owed but the last payment is // less in value than regular payments. That means, that the // future value is to be treated as (fully payed back) // c) the loan is not payed back yet if ((field("borrowButton").toBool() && val < 0 && qAbs(val) > qAbs(calc.payment())) || (field("lendButton").toBool() && val > 0 && qAbs(val) > qAbs(calc.payment()))) { // case a) qDebug("Future Value is %f", val); throw MYMONEYEXCEPTION("incorrect fincancial calculation"); } else if ((field("borrowButton").toBool() && val < 0 && qAbs(val) <= qAbs(calc.payment())) || (field("lendButton").toBool() && val > 0 && qAbs(val) <= qAbs(calc.payment()))) { // case b) val = 0; } MyMoneyMoney refVal(static_cast<double>(val)); result = i18n("KMyMoney has calculated a final payment of %1 for this loan.", refVal.abs().formatMoney(fraction)); if (field("finalPaymentEditValid").toBool()) { if ((field("finalPaymentEdit").value<MyMoneyMoney>().abs() - refVal.abs()).abs().toDouble() > 1) { throw MYMONEYEXCEPTION("incorrect fincancial calculation"); } result = i18n("KMyMoney has successfully verified your loan information."); } //FIXME: port m_finalPaymentPage->m_finalPaymentEdit->loadText(refVal.abs().formatMoney(fraction)); } } catch (const MyMoneyException &) { KMessageBox::error(0, i18n("You have entered mis-matching information. Please backup to the " "appropriate page and update your figures or leave one value empty " "to let KMyMoney calculate it for you"), i18n("Calculation error")); return 0; } result += i18n("\n\nAccept this or modify the loan information and recalculate."); KMessageBox::information(0, result, i18n("Calculation successful")); return 1; }