diff --git a/kmymoney/wizards/newloanwizard/keditloanwizard.cpp b/kmymoney/wizards/newloanwizard/keditloanwizard.cpp index 842fc3481..c1e21f90d 100644 --- a/kmymoney/wizards/newloanwizard/keditloanwizard.cpp +++ b/kmymoney/wizards/newloanwizard/keditloanwizard.cpp @@ -1,551 +1,552 @@ /*************************************************************************** keditloanwizard.cpp - description ------------------- begin : Wed Nov 12 2003 copyright : (C) 2000-2003 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "keditloanwizard.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_interesteditwizardpage.h" #include "ui_paymentfrequencywizardpage.h" #include "ui_paymenteditwizardpage.h" #include "ui_loanattributeswizardpage.h" #include "ui_effectivedatewizardpage.h" #include "ui_interesttypewizardpage.h" #include "ui_editselectionwizardpage.h" #include "ui_finalpaymentwizardpage.h" #include "knewloanwizard.h" #include "knewloanwizard_p.h" #include "kmymoneylineedit.h" #include "kmymoneyedit.h" #include "kmymoneyaccountselector.h" #include "mymoneyfile.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneyaccountloan.h" #include "mymoneypayee.h" #include "mymoneyschedule.h" #include "mymoneytransactionfilter.h" class KEditLoanWizardPrivate : public KNewLoanWizardPrivate { Q_DISABLE_COPY(KEditLoanWizardPrivate) public: KEditLoanWizardPrivate(KEditLoanWizard *qq) : KNewLoanWizardPrivate(qq), m_lastSelection(0), m_fullyRepayLoan(false) { } MyMoneySchedule m_schedule; int m_lastSelection; bool m_fullyRepayLoan; }; KEditLoanWizard::KEditLoanWizard(const MyMoneyAccount& account, QWidget *parent) : KNewLoanWizard(*new KEditLoanWizardPrivate(this), parent) { Q_D(KEditLoanWizard); auto file = MyMoneyFile::instance(); setWindowTitle(i18n("Edit loan wizard")); d->m_account = account; try { QString id = d->m_account.value("schedule"); d->m_schedule = file->schedule(id); } catch (const MyMoneyException &) { } d->m_lastSelection = -1; loadWidgets(d->m_account); if (d->m_account.openingDate() > QDate::currentDate()) { //FIXME: port d->ui->m_effectiveDatePage->ui->m_effectiveDateNoteLabel->setText(QString("\n") + i18n( "Note: you will not be able to modify this account today, because the opening date \"%1\" is in the future. " "Please revisit this dialog when the time has come.", QLocale().toString(d->m_account.openingDate()))); } else { d->ui->m_effectiveDatePage->ui->m_effectiveDateNoteLabel->hide(); } // turn off all pages that are contained here for derived classes d->m_pages.clearBit(Page_Intro); d->m_pages.clearBit(Page_NewGeneralInfo); d->m_pages.clearBit(Page_LendBorrow); d->m_pages.clearBit(Page_Name); d->m_pages.clearBit(Page_NewCalculateLoan); d->m_pages.clearBit(Page_NewPayments); removePage(Page_AssetAccount); d->ui->m_assetAccountPage = 0; // turn on all pages that are contained here for derived classes d->m_pages.setBit(Page_EditIntro); d->m_pages.setBit(Page_EditSelection); // make sure, we show the correct start page setStartId(Page_EditIntro); } KEditLoanWizard::~KEditLoanWizard() { } void KEditLoanWizard::loadWidgets(const MyMoneyAccount& /* account */) { Q_D(KEditLoanWizard); auto file = MyMoneyFile::instance(); QString paymentAccountId, interestAccountId; //FIXME: port d->ui->m_namePage->ui->m_nameEdit->loadText(d->m_account.name()); d->ui->m_loanAmountPage->ui->m_loanAmountEdit->loadText(d->m_account.loanAmount().formatMoney(d->m_account.fraction(MyMoneyFile::instance()->security(d->m_account.currencyId())))); d->ui->m_finalPaymentPage->ui->m_finalPaymentEdit->loadText(d->m_account.finalPayment().formatMoney(d->m_account.fraction(MyMoneyFile::instance()->security(d->m_account.currencyId())))); setField("firstDueDateEdit", d->m_account.openingDate()); //FIXME: port if (d->m_account.fixedInterestRate()) { d->ui->m_interestTypePage->ui->m_fixedInterestButton->click(); } else { d->ui->m_interestTypePage->ui->m_variableInterestButton->click(); } QString institutionName; try { institutionName = file->institution(d->m_account.institutionId()).name(); } catch (const MyMoneyException &) { } d->ui->m_loanAttributesPage->setInstitution(institutionName); MyMoneyMoney ir; if (d->m_schedule.startDate() > QDate::currentDate()) { ir = d->m_account.interestRate(d->m_schedule.startDate()); } else { ir = d->m_account.interestRate(QDate::currentDate()); } //FIXME: port d->ui->m_interestPage->ui->m_interestRateEdit->setPrecision(3); d->ui->m_interestPage->ui->m_interestRateEdit->loadText(ir.formatMoney(QString(), 3)); d->ui->m_interestEditPage->ui->m_newInterestRateEdit->setPrecision(3); d->ui->m_interestEditPage->ui->m_newInterestRateEdit->loadText(ir.formatMoney(QString(), 3)); d->ui->m_interestEditPage->ui->m_interestRateLabel->setText(QString(" ") + ir.formatMoney(QString(), 3) + QString("%")); d->ui->m_paymentFrequencyPage->ui->m_paymentFrequencyUnitEdit->setCurrentIndex(d->ui->m_paymentFrequencyPage->ui->m_paymentFrequencyUnitEdit->findData(QVariant((int)d->m_schedule.occurrence()), Qt::UserRole, Qt::MatchExactly)); d->ui->m_durationPage->updateTermWidgets(d->m_account.term()); // the base payment (amortization and interest) is determined // by adding all splits that are not automatically calculated. // If the loan is a liability, we reverse the sign at the end MyMoneyMoney basePayment; MyMoneyMoney addPayment; d->m_transaction = d->m_schedule.transaction(); foreach (const MyMoneySplit& it_s, d->m_schedule.transaction().splits()) { MyMoneyAccount acc = file->account(it_s.accountId()); // if it's the split that references the source/dest // of the money, we check if we borrow or loan money if (paymentAccountId.isEmpty() && acc.isAssetLiability() && !acc.isLoan() && it_s.value() != MyMoneyMoney::autoCalc) { if (it_s.value().isNegative()) { setField("lendButton", false); setField("borrowButton", true); } else { setField("lendButton", true); setField("borrowButton", false); } // we keep the amount of the full payment and subtract the // base payment later to get information about the additional payment addPayment = it_s.value(); paymentAccountId = it_s.accountId(); MyMoneyPayee payee; if (!it_s.payeeId().isEmpty()) { try { payee = file->payee(it_s.payeeId()); setField("payeeEdit", payee.id()); } catch (const MyMoneyException &) { qWarning("Payee for schedule has been deleted"); } } // remove this split with one that will be replaced // later and has a phony id d->m_transaction.removeSplit(it_s); d->m_split.clearId(); d->m_transaction.addSplit(d->m_split); } if (it_s.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) { interestAccountId = it_s.accountId(); } if (it_s.value() != MyMoneyMoney::autoCalc) { basePayment += it_s.value(); } else { // remove the splits which should not show up // for additional fees d->m_transaction.removeSplit(it_s); } } if (field("borrowButton").toBool()) { basePayment = -basePayment; addPayment = -addPayment; } // now make adjustment to get the amount of the additional fees addPayment -= basePayment; // load account selection widgets now that we know if // we borrow or lend money d->loadAccountList(); int fraction = d->m_account.fraction(MyMoneyFile::instance()->security(d->m_account.currencyId())); //FIXME: port d->ui->m_paymentPage->ui->m_paymentEdit->loadText(basePayment.formatMoney(fraction)); d->ui->m_paymentEditPage->ui->m_newPaymentEdit->loadText(basePayment.formatMoney(fraction)); d->ui->m_paymentEditPage->ui->m_paymentLabel->setText(QString(" ") + basePayment.formatMoney(fraction)); setField("additionalCost", addPayment.formatMoney(fraction)); d->ui->m_interestCategoryPage->ui->m_interestAccountEdit->setSelected(interestAccountId); d->ui->m_schedulePage->ui->m_paymentAccountEdit->setSelected(paymentAccountId); setField("nextDueDateEdit", d->m_schedule.nextPayment()); int changeFrequencyUnit; int amt = d->m_account.interestChangeFrequency(&changeFrequencyUnit); if (amt != -1) { setField("interestFrequencyAmountEdit", amt); setField("interestFrequencyUnitEdit", changeFrequencyUnit); } // keep track, if the loan should be fully repayed d->m_fullyRepayLoan = d->m_account.finalPayment() < basePayment; d->updateLoanInfo(); } bool KEditLoanWizard::validateCurrentPage() { Q_D(KEditLoanWizard); auto dontLeavePage = false; //FIXME: port m_lastSelection QAbstractButton* button = d->ui->m_editSelectionPage->ui->m_selectionButtonGroup->button(d->m_lastSelection); if (currentPage() == d->ui->m_editSelectionPage) { if (button != 0 && d->m_lastSelection != d->ui->m_editSelectionPage->ui->m_selectionButtonGroup->checkedId()) { QString errMsg = i18n( "Your previous selection was \"%1\". If you select another option, " "KMyMoney will dismiss the changes you have just entered. " "Do you wish to proceed?", button->text()); if (KMessageBox::questionYesNo(this, errMsg) == KMessageBox::No) { dontLeavePage = true; } else { loadWidgets(d->m_account); } } if (!dontLeavePage) { // turn off all pages except the summary at the end // and the one's we need for the selected option // and load the widgets with the current values // general info d->m_pages.clearBit(Page_Name); d->m_pages.clearBit(Page_InterestType); d->m_pages.clearBit(Page_PreviousPayments); d->m_pages.clearBit(Page_RecordPayment); d->m_pages.clearBit(Page_VariableInterestDate); d->m_pages.clearBit(Page_FirstPayment); // loan calculation d->m_pages.clearBit(Page_PaymentEdit); d->m_pages.clearBit(Page_InterestEdit); d->m_pages.clearBit(Page_PaymentFrequency); d->m_pages.clearBit(Page_InterestCalculation); d->m_pages.clearBit(Page_LoanAmount); d->m_pages.clearBit(Page_Interest); d->m_pages.clearBit(Page_Duration); d->m_pages.clearBit(Page_Payment); d->m_pages.clearBit(Page_FinalPayment); d->m_pages.clearBit(Page_CalculationOverview); // payment d->m_pages.clearBit(Page_InterestCategory); d->m_pages.clearBit(Page_AdditionalFees); d->m_pages.clearBit(Page_Schedule); d->m_pages.setBit(Page_Summary); // Attributes d->m_pages.clearBit(Page_LoanAttributes); d->m_pages.setBit(Page_EffectiveDate); if (page(Page_Summary) != 0) { removePage(Page_Summary); } if (field("editInterestRateButton").toBool()) { d->m_pages.setBit(Page_PaymentFrequency); d->m_pages.setBit(Page_InterestType); d->m_pages.setBit(Page_VariableInterestDate); d->m_pages.setBit(Page_PaymentEdit); d->m_pages.setBit(Page_InterestEdit); d->m_pages.setBit(Page_InterestCategory); d->m_pages.setBit(Page_Schedule); d->m_pages.setBit(Page_SummaryEdit); } else if (field("editOtherCostButton").toBool()) { d->m_pages.setBit(Page_PaymentFrequency); d->m_pages.setBit(Page_AdditionalFees); d->m_pages.setBit(Page_InterestCategory); d->m_pages.setBit(Page_Schedule); d->m_pages.setBit(Page_SummaryEdit); } else if (field("editOtherInfoButton").toBool()) { d->m_pages.setBit(Page_Name); d->m_pages.setBit(Page_InterestCalculation); d->m_pages.setBit(Page_Interest); d->m_pages.setBit(Page_Duration); d->m_pages.setBit(Page_Payment); d->m_pages.setBit(Page_FinalPayment); d->m_pages.setBit(Page_CalculationOverview); d->m_pages.setBit(Page_InterestCategory); d->m_pages.setBit(Page_AdditionalFees); d->m_pages.setBit(Page_Schedule); d->m_pages.clearBit(Page_SummaryEdit); setPage(Page_Summary, d->ui->m_summaryPage); d->m_pages.setBit(Page_Summary); } else if (field("editAttributesButton").toBool()) { d->m_pages.setBit(Page_LoanAttributes); d->m_pages.clearBit(Page_EffectiveDate); } else { qWarning("%s,%d: This should never happen", __FILE__, __LINE__); } d->m_lastSelection = d->ui->m_editSelectionPage->ui->m_selectionButtonGroup->checkedId(); } // if(!dontLeavePage) } else if (currentPage() == d->ui->m_additionalFeesPage) { if (field("editOtherCostButton").toBool()) { d->updateLoanInfo(); updateEditSummary(); } } else if (currentPage() == d->ui->m_interestEditPage) { // copy the necessary data to the widgets used for calculation //FIXME: port to fields d->ui->m_interestPage->ui->m_interestRateEdit->setValue(field("newInterestRateEdit").value()); d->ui->m_paymentPage->ui->m_paymentEdit->setValue(field("newPaymentEdit").value()); // if interest rate and payment amount is given, then force // the term to be recalculated. The final payment is adjusted to // 0 if the loan was ment to be fully repayed d->ui->m_durationPage->updateTermWidgets(d->m_account.term()); if (field("interestRateEditValid").toBool() && field("paymentEditValid").toBool()) { // if there's an amortization going on, we can evaluate // the new term. If the amortization is 0 (interest only // payments) then we keep the term as entered by the user. if (field("loanAmountEdit").value() != field("finalPaymentEdit").value()) { setField("durationValueEdit", 0); } if (d->m_fullyRepayLoan) d->ui->m_finalPaymentPage->ui->m_finalPaymentEdit->loadText(MyMoneyMoney().formatMoney(d->m_account.fraction(MyMoneyFile::instance()->security(d->m_account.currencyId())))); } /* // we need to calculate the balance at the time of the change // in order to accurately recalculate the term. A special // situation arises, when we keep track of all payments and // the full loan is not yet paid out. In this case, we take the // the loan amount minus all amortization payments as the current // balance. // FIXME: This needs some more thoughts. We leave it out for // now and always calculate with the full loan amount. MyMoneyMoney balance = m_account.openingBalance(); QList list; QList::ConstIterator it; MyMoneySplit split; MyMoneyTransactionFilter filter(m_account.id()); filter.setDateFilter(QDate(), m_effectiveChangeDateEdit->date().addDays(-1)); list = MyMoneyFile::instance()->transactionList(filter); for(it = list.begin(); it != list.end(); ++it) { try { split = (*it).splitByAccount(m_account.id()); balance += split.value(); } catch (const MyMoneyException &e) { // account is not referenced within this transaction } } m_loanAmountEdit->setText(balance.formatMoney()); */ // now re-calculate the figures dontLeavePage = !d->calculateLoan(); // reset the original loan amount to the widget //FIXME: port to fields d->ui->m_loanAmountPage->ui->m_loanAmountEdit->setValue(d->m_account.loanAmount()); if (!dontLeavePage) { d->updateLoanInfo(); updateEditSummary(); } } if (!dontLeavePage) dontLeavePage = ! KNewLoanWizard::validateCurrentPage(); // These might have been set by KNewLoanWizard d->m_pages.clearBit(Page_PreviousPayments); d->m_pages.clearBit(Page_RecordPayment); if (dontLeavePage) return false; // we never need to show this page if (currentPage() == d->ui->m_previousPaymentsPage) dontLeavePage = KNewLoanWizard::validateCurrentPage(); return ! dontLeavePage; } void KEditLoanWizard::updateEditSummary() { Q_D(KEditLoanWizard); // calculate the number of affected transactions MyMoneyTransactionFilter filter(d->m_account.id()); filter.setDateFilter(field("effectiveChangeDateEdit").toDate(), QDate()); int count = 0; QList list; list = MyMoneyFile::instance()->transactionList(filter); foreach (const MyMoneyTransaction& it, list) { int match = 0; foreach (const MyMoneySplit& it_s, it.splits()) { // we only count those transactions that have an interest // and amortization part if (it_s.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) match |= 0x01; if (it_s.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization)) match |= 0x02; } if (match == 0x03) ++count; } setField("affectedPayments", QString().sprintf("%d", count)); } const MyMoneySchedule KEditLoanWizard::schedule() const { Q_D(const KEditLoanWizard); MyMoneySchedule sched = d->m_schedule; sched.setTransaction(transaction()); sched.setOccurrence(eMyMoney::Schedule::Occurrence(field("paymentFrequencyUnitEdit").toInt())); if (field("nextDueDateEdit").toDate() < d->m_schedule.startDate()) sched.setStartDate(field("nextDueDateEdit").toDate()); return sched; } const MyMoneyAccount KEditLoanWizard::account() const { Q_D(const KEditLoanWizard); MyMoneyAccountLoan acc(d->m_account); if (field("interestOnReceptionButton").toBool()) acc.setInterestCalculation(MyMoneyAccountLoan::paymentReceived); else acc.setInterestCalculation(MyMoneyAccountLoan::paymentDue); auto file = MyMoneyFile::instance(); QString institution = d->ui->m_loanAttributesPage->ui->m_qcomboboxInstitutions->currentText(); if (institution != i18n("(No Institution)")) { const auto list = file->institutionList(); for (const auto& testInstitution : list) { if (testInstitution.name() == institution) { acc.setInstitutionId(testInstitution.id()); break; } } } else { acc.setInstitutionId(QString()); } acc.setName(d->ui->m_namePage->ui->m_nameEdit->text()); acc.setFixedInterestRate(field("fixedInterestButton").toBool()); acc.setFinalPayment(field("finalPaymentEdit").value()); acc.setTerm(d->ui->m_durationPage->term()); acc.setPeriodicPayment(field("paymentEdit").value()); acc.setInterestRate(field("effectiveChangeDateEdit").toDate(), field("interestRateEdit").value()); acc.setPayee(field("payeeEdit").toString()); if (field("variableInterestButton").toBool()) { acc.setNextInterestChange(field("interestChangeDateEdit").toDate()); acc.setInterestChangeFrequency(field("interestFrequencyAmountEdit").toInt(), field("interestFrequencyUnitEdit").toInt()); } return acc; } const MyMoneyTransaction KEditLoanWizard::transaction() const { Q_D(const KEditLoanWizard); auto t = d->transaction(); - auto s = t.splitByAccount(QString("Phony-ID")); - - s.setAccountId(d->m_account.id()); - t.modifySplit(s); + if (t.splitCount() > 1) { + auto s = t.splitByAccount(QString("Phony-ID")); + s.setAccountId(d->m_account.id()); + t.modifySplit(s); + } return t; } diff --git a/kmymoney/wizards/newloanwizard/knewloanwizard_p.h b/kmymoney/wizards/newloanwizard/knewloanwizard_p.h index 0df519d72..3dd386bbd 100644 --- a/kmymoney/wizards/newloanwizard/knewloanwizard_p.h +++ b/kmymoney/wizards/newloanwizard/knewloanwizard_p.h @@ -1,542 +1,543 @@ /*************************************************************************** knewloanwizard_p.cpp - description ------------------- copyright : (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KNEWLOANWIZARD_P_H #define KNEWLOANWIZARD_P_H #include "knewloanwizard.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_knewloanwizard.h" #include "ui_namewizardpage.h" #include "ui_firstpaymentwizardpage.h" #include "ui_loanamountwizardpage.h" #include "ui_interestwizardpage.h" #include "ui_paymenteditwizardpage.h" #include "ui_finalpaymentwizardpage.h" #include "ui_interestcategorywizardpage.h" #include "ui_assetaccountwizardpage.h" #include "ui_schedulewizardpage.h" #include "ui_paymentwizardpage.h" #include "kmymoneyutils.h" #include "kmymoneysettings.h" #include "mymoneyfinancialcalculator.h" #include "mymoneyfile.h" #include "mymoneyexception.h" #include "mymoneysecurity.h" #include "mymoneyaccountloan.h" #include "mymoneyschedule.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneyenums.h" namespace Ui { class KNewLoanWizard; } class KNewLoanWizard; class KNewLoanWizardPrivate { Q_DISABLE_COPY(KNewLoanWizardPrivate) Q_DECLARE_PUBLIC(KNewLoanWizard) public: explicit KNewLoanWizardPrivate(KNewLoanWizard *qq) : q_ptr(qq), ui(new Ui::KNewLoanWizard) { } ~KNewLoanWizardPrivate() { delete ui; } void init() { Q_Q(KNewLoanWizard); ui->setupUi(q); m_pages = QBitArray(KNewLoanWizard::Page_Summary + 1, true); q->setModal(true); KMyMoneyMVCCombo::setSubstringSearchForChildren(ui->m_namePage, !KMyMoneySettings::stringMatchFromStart()); // make sure, the back button does not clear fields q->setOption(QWizard::IndependentPages, true); // connect(m_payeeEdit, SIGNAL(newPayee(QString)), this, SLOT(slotNewPayee(QString))); q->connect(ui->m_namePage->ui->m_payeeEdit, &KMyMoneyMVCCombo::createItem, q, &KNewLoanWizard::slotNewPayee); q->connect(ui->m_additionalFeesPage, &AdditionalFeesWizardPage::newCategory, q, &KNewLoanWizard::slotNewCategory); q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KNewLoanWizard::slotReloadEditWidgets); resetCalculator(); q->slotReloadEditWidgets(); // As default we assume a liability loan, with fixed interest rate, // with a first payment due on the 30th of this month. All payments // should be recorded and none have been made so far. //FIXME: port ui->m_firstPaymentPage->ui->m_firstDueDateEdit->loadDate(QDate(QDate::currentDate().year(), QDate::currentDate().month(), 30)); // FIXME: we currently only support interest calculation on reception m_pages.clearBit(KNewLoanWizard::Page_InterestCalculation); // turn off all pages that are contained here for derived classes m_pages.clearBit(KNewLoanWizard::Page_EditIntro); m_pages.clearBit(KNewLoanWizard::Page_EditSelection); m_pages.clearBit(KNewLoanWizard::Page_EffectiveDate); m_pages.clearBit(KNewLoanWizard::Page_PaymentEdit); m_pages.clearBit(KNewLoanWizard::Page_InterestEdit); m_pages.clearBit(KNewLoanWizard::Page_SummaryEdit); // for now, we don't have online help :-( q->setOption(QWizard::HaveHelpButton, false); // setup a phony transaction for additional fee processing m_account = MyMoneyAccount("Phony-ID", MyMoneyAccount()); m_split.setAccountId(m_account.id()); m_split.setValue(MyMoneyMoney()); m_transaction.addSplit(m_split); KMyMoneyUtils::updateWizardButtons(q); } void resetCalculator() { Q_Q(KNewLoanWizard); ui->m_loanAmountPage->resetCalculator(); ui->m_interestPage->resetCalculator(); ui->m_durationPage->resetCalculator(); ui->m_paymentPage->resetCalculator(); ui->m_finalPaymentPage->resetCalculator(); q->setField("additionalCost", MyMoneyMoney().formatMoney(m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId())))); } void updateLoanAmount() { Q_Q(KNewLoanWizard); QString txt; //FIXME: port if (! q->field("loanAmountEditValid").toBool()) { txt = QString("<") + i18n("calculate") + QString(">"); } else { txt = q->field("loanAmountEdit").value().formatMoney(m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId()))); } q->setField("loanAmount1", txt); q->setField("loanAmount2", txt); q->setField("loanAmount3", txt); q->setField("loanAmount4", txt); q->setField("loanAmount5", txt); } void updateInterestRate() { Q_Q(KNewLoanWizard); QString txt; //FIXME: port if (! q->field("interestRateEditValid").toBool()) { txt = QString("<") + i18n("calculate") + QString(">"); } else { txt = q->field("interestRateEdit").value().formatMoney(QString(), 3) + QString("%"); } q->setField("interestRate1", txt); q->setField("interestRate2", txt); q->setField("interestRate3", txt); q->setField("interestRate4", txt); q->setField("interestRate5", txt); } void updateDuration() { Q_Q(KNewLoanWizard); QString txt; //FIXME: port if (q->field("durationValueEdit").toInt() == 0) { txt = QString("<") + i18n("calculate") + QString(">"); } else { txt = QString().sprintf("%d ", q->field("durationValueEdit").toInt()) + q->field("durationUnitEdit").toString(); } q->setField("duration1", txt); q->setField("duration2", txt); q->setField("duration3", txt); q->setField("duration4", txt); q->setField("duration5", txt); } void updatePayment() { Q_Q(KNewLoanWizard); QString txt; //FIXME: port if (! q->field("paymentEditValid").toBool()) { txt = QString("<") + i18n("calculate") + QString(">"); } else { txt = q->field("paymentEdit").value().formatMoney(m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId()))); } q->setField("payment1", txt); q->setField("payment2", txt); q->setField("payment3", txt); q->setField("payment4", txt); q->setField("payment5", txt); q->setField("basePayment", txt); } void updateFinalPayment() { Q_Q(KNewLoanWizard); QString txt; //FIXME: port if (! q->field("finalPaymentEditValid").toBool()) { txt = QString("<") + i18n("calculate") + QString(">"); } else { txt = q->field("finalPaymentEdit").value().formatMoney(m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId()))); } q->setField("balloon1", txt); q->setField("balloon2", txt); q->setField("balloon3", txt); q->setField("balloon4", txt); q->setField("balloon5", txt); } void updateLoanInfo() { Q_Q(KNewLoanWizard); updateLoanAmount(); updateInterestRate(); updateDuration(); updatePayment(); updateFinalPayment(); ui->m_additionalFeesPage->updatePeriodicPayment(m_account); QString txt; int fraction = m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId())); q->setField("loanAmount6", q->field("loanAmountEdit").value().formatMoney(fraction)); q->setField("interestRate6", QString(q->field("interestRateEdit").value().formatMoney("", 3) + QString("%"))); txt = QString().sprintf("%d ", q->field("durationValueEdit").toInt()) + q->field("durationUnitEdit").toString(); q->setField("duration6", txt); q->setField("payment6", q->field("paymentEdit").value().formatMoney(fraction)); q->setField("balloon6", q->field("finalPaymentEdit").value().formatMoney(fraction)); } int calculateLoan() { Q_Q(KNewLoanWizard); 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(eMyMoney::Schedule::Occurrence(q->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 (q->field("loanAmountEditValid").toBool()) { val = q->field("loanAmountEdit").value().abs().toDouble(); if (q->field("borrowButton").toBool()) val = -val; calc.setPv(val); } if (q->field("interestRateEditValid").toBool()) { val = q->field("interestRateEdit").value().abs().toDouble(); calc.setIr(val); } if (q->field("paymentEditValid").toBool()) { val = q->field("paymentEdit").value().abs().toDouble(); if (q->field("lendButton").toBool()) val = -val; calc.setPmt(val); } if (q->field("finalPaymentEditValid").toBool()) { val = q->field("finalPaymentEditValid").value().abs().toDouble(); if (q->field("lendButton").toBool()) val = -val; calc.setFv(val); } if (q->field("durationValueEdit").toInt() != 0) { calc.setNpp(ui->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 (!q->field("loanAmountEditValid").toBool()) { // calculate the amount of the loan out of the other information val = calc.presentValue(); ui->m_loanAmountPage->ui->m_loanAmountEdit->loadText(MyMoneyMoney(static_cast(val)).abs().formatMoney(fraction)); result = i18n("KMyMoney has calculated the amount of the loan as %1.", ui->m_loanAmountPage->ui->m_loanAmountEdit->lineedit()->text()); } else if (!q->field("interestRateEditValid").toBool()) { // calculate the interest rate out of the other information val = calc.interestRate(); ui->m_interestPage->ui->m_interestRateEdit->loadText(MyMoneyMoney(static_cast(val)).abs().formatMoney("", 3)); result = i18n("KMyMoney has calculated the interest rate to %1%.", ui->m_interestPage->ui->m_interestRateEdit->lineedit()->text()); } else if (!q->field("paymentEditValid").toBool()) { // calculate the periodical amount of the payment out of the other information val = calc.payment(); q->setField("paymentEdit", QVariant::fromValue(MyMoneyMoney(val).abs())); // reset payment as it might have changed due to rounding val = q->field("paymentEdit").value().abs().toDouble(); if (q->field("lendButton").toBool()) val = -val; calc.setPmt(val); result = i18n("KMyMoney has calculated a periodic payment of %1 to cover principal and interest.", ui->m_paymentPage->ui->m_paymentEdit->lineedit()->text()); val = calc.futureValue(); if ((q->field("borrowButton").toBool() && val < 0 && qAbs(val) >= qAbs(calc.payment())) || (q->field("lendButton").toBool() && val > 0 && qAbs(val) >= qAbs(calc.payment()))) { calc.setNpp(calc.npp() - 1); ui->m_durationPage->updateTermWidgets(calc.npp()); val = calc.futureValue(); MyMoneyMoney refVal(static_cast(val)); ui->m_finalPaymentPage->ui->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.", ui->m_finalPaymentPage->ui->m_finalPaymentEdit->lineedit()->text()); } else if ((q->field("borrowButton").toBool() && val < 0 && qAbs(val) < qAbs(calc.payment())) || (q->field("lendButton").toBool() && val > 0 && qAbs(val) < qAbs(calc.payment()))) { ui->m_finalPaymentPage->ui->m_finalPaymentEdit->loadText(MyMoneyMoney().formatMoney(fraction)); } else { MyMoneyMoney refVal(static_cast(val)); ui->m_finalPaymentPage->ui->m_finalPaymentEdit->loadText(refVal.abs().formatMoney(fraction)); result += i18n("The final payment has been modified to %1.", ui->m_finalPaymentPage->ui->m_finalPaymentEdit->lineedit()->text()); } } else if (q->field("durationValueEdit").toInt() == 0) { // calculate the number of payments out of the other information val = calc.numPayments(); if (val == 0) throw MYMONEYEXCEPTION_CSTRING("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. ", ui->m_durationPage->updateTermWidgets(qFloor(val))); if (val != qFloor(val)) { calc.setNpp(qFloor(val)); val = calc.futureValue(); MyMoneyMoney refVal(static_cast(val)); ui->m_finalPaymentPage->ui->m_finalPaymentEdit->loadText(refVal.abs().formatMoney(fraction)); result += i18n("The final payment has been modified to %1.", ui->m_finalPaymentPage->ui->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 ((q->field("borrowButton").toBool() && val < 0 && qAbs(val) > qAbs(calc.payment())) || (q->field("lendButton").toBool() && val > 0 && qAbs(val) > qAbs(calc.payment()))) { // case a) qDebug("Future Value is %f", val); throw MYMONEYEXCEPTION_CSTRING("incorrect fincancial calculation"); } else if ((q->field("borrowButton").toBool() && val < 0 && qAbs(val) <= qAbs(calc.payment())) || (q->field("lendButton").toBool() && val > 0 && qAbs(val) <= qAbs(calc.payment()))) { // case b) val = 0; } MyMoneyMoney refVal(static_cast(val)); result = i18n("KMyMoney has calculated a final payment of %1 for this loan.", refVal.abs().formatMoney(fraction)); if (q->field("finalPaymentEditValid").toBool()) { if ((q->field("finalPaymentEdit").value().abs() - refVal.abs()).abs().toDouble() > 1) { throw MYMONEYEXCEPTION_CSTRING("incorrect fincancial calculation"); } result = i18n("KMyMoney has successfully verified your loan information."); } //FIXME: port ui->m_finalPaymentPage->ui->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; } /** * This method returns the transaction that is stored within * the schedule. See schedule(). * * @return MyMoneyTransaction object to be used within the schedule */ MyMoneyTransaction transaction() const { Q_Q(const KNewLoanWizard); MyMoneyTransaction t; bool hasInterest = !q->field("interestRateEdit").value().isZero(); MyMoneySplit sPayment, sInterest, sAmortization; // setup accounts. at this point, we cannot fill in the id of the // account that the amortization will be performed on, because we - // create the account. So the id is yet unknown. + // create the account. So the id is yet unknown. But all others + // must exist, otherwise we cannot create a schedule. sPayment.setAccountId(q->field("paymentAccountEdit").toStringList().first()); + if (!sPayment.accountId().isEmpty()) { + + //Only create the interest split if not zero + if (hasInterest) { + sInterest.setAccountId(q->field("interestAccountEdit").toStringList().first()); + sInterest.setValue(MyMoneyMoney::autoCalc); + sInterest.setShares(sInterest.value()); + sInterest.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)); + } + // values + if (q->field("borrowButton").toBool()) { + sPayment.setValue(-q->field("paymentEdit").value()); + } else { + sPayment.setValue(q->field("paymentEdit").value()); + } - //Only create the interest split if not zero - if (hasInterest) { - sInterest.setAccountId(q->field("interestAccountEdit").toStringList().first()); - sInterest.setValue(MyMoneyMoney::autoCalc); - sInterest.setShares(sInterest.value()); - sInterest.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)); - } - - // values - if (q->field("borrowButton").toBool()) { - sPayment.setValue(-q->field("paymentEdit").value()); - } else { - sPayment.setValue(q->field("paymentEdit").value()); - } - - sAmortization.setValue(MyMoneyMoney::autoCalc); - // don't forget the shares - sPayment.setShares(sPayment.value()); + sAmortization.setValue(MyMoneyMoney::autoCalc); + // don't forget the shares + sPayment.setShares(sPayment.value()); - sAmortization.setShares(sAmortization.value()); + sAmortization.setShares(sAmortization.value()); - // setup the commodity - MyMoneyAccount acc = MyMoneyFile::instance()->account(sPayment.accountId()); - t.setCommodity(acc.currencyId()); + // setup the commodity + MyMoneyAccount acc = MyMoneyFile::instance()->account(sPayment.accountId()); + t.setCommodity(acc.currencyId()); - // actions - sPayment.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization)); - sAmortization.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization)); + // actions + sPayment.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization)); + sAmortization.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization)); - // payee - QString payeeId = q->field("payeeEdit").toString(); - sPayment.setPayeeId(payeeId); - sAmortization.setPayeeId(payeeId); + // payee + QString payeeId = q->field("payeeEdit").toString(); + sPayment.setPayeeId(payeeId); + sAmortization.setPayeeId(payeeId); - MyMoneyAccount account("Phony-ID", MyMoneyAccount()); - sAmortization.setAccountId(account.id()); + sAmortization.setAccountId(QStringLiteral("Phony-ID")); - // IMPORTANT: Payment split must be the first one, because - // the schedule view expects it this way during display - t.addSplit(sPayment); - t.addSplit(sAmortization); + // IMPORTANT: Payment split must be the first one, because + // the schedule view expects it this way during display + t.addSplit(sPayment); + t.addSplit(sAmortization); - if (hasInterest) { - t.addSplit(sInterest); - } + if (hasInterest) { + t.addSplit(sInterest); + } - // copy the splits from the other costs and update the payment split - foreach (const MyMoneySplit& it, m_transaction.splits()) { - if (it.accountId() != account.id()) { - MyMoneySplit sp = it; - sp.clearId(); - t.addSplit(sp); - sPayment.setValue(sPayment.value() - sp.value()); - sPayment.setShares(sPayment.value()); - t.modifySplit(sPayment); + // copy the splits from the other costs and update the payment split + foreach (const MyMoneySplit& it, m_transaction.splits()) { + if (it.accountId() != QStringLiteral("Phony-ID")) { + MyMoneySplit sp = it; + sp.clearId(); + t.addSplit(sp); + sPayment.setValue(sPayment.value() - sp.value()); + sPayment.setShares(sPayment.value()); + t.modifySplit(sPayment); + } } } return t; } void loadAccountList() { Q_Q(KNewLoanWizard); AccountSet interestSet, assetSet; if (q->field("borrowButton").toBool()) { interestSet.addAccountType(eMyMoney::Account::Type::Expense); } else { interestSet.addAccountType(eMyMoney::Account::Type::Income); } if (ui->m_interestCategoryPage) interestSet.load(ui->m_interestCategoryPage->ui->m_interestAccountEdit); assetSet.addAccountType(eMyMoney::Account::Type::Checkings); assetSet.addAccountType(eMyMoney::Account::Type::Savings); assetSet.addAccountType(eMyMoney::Account::Type::Cash); assetSet.addAccountType(eMyMoney::Account::Type::Asset); assetSet.addAccountType(eMyMoney::Account::Type::Currency); if (ui->m_assetAccountPage) assetSet.load(ui->m_assetAccountPage->ui->m_assetAccountEdit); assetSet.addAccountType(eMyMoney::Account::Type::CreditCard); assetSet.addAccountType(eMyMoney::Account::Type::Liability); if (ui->m_schedulePage) assetSet.load(ui->m_schedulePage->ui->m_paymentAccountEdit); } KNewLoanWizard *q_ptr; Ui::KNewLoanWizard *ui; MyMoneyAccountLoan m_account; MyMoneyTransaction m_transaction; MyMoneySplit m_split; QBitArray m_pages; }; #endif