MyMoneyInstitution::MyMoneyInstitution(const QDomElement& node) : MyMoneyObject(node), MyMoneyKeyValueContainer(node.elementsByTagName("KEYVALUEPAIRS").item(0).toElement()) { if ("INSTITUTION" != node.tagName()) throw MYMONEYEXCEPTION("Node was not INSTITUTION"); m_sortcode = node.attribute("sortcode"); m_name = node.attribute("name"); m_manager = node.attribute("manager"); QDomNodeList nodeList = node.elementsByTagName("ADDRESS"); if (nodeList.count() == 0) { QString msg = QString("No ADDRESS in institution %1").arg(m_name); throw MYMONEYEXCEPTION(msg); } QDomElement addrNode = nodeList.item(0).toElement(); m_street = addrNode.attribute("street"); m_town = addrNode.attribute("city"); m_postcode = addrNode.attribute("zip"); m_telephone = addrNode.attribute("telephone"); m_accountList.clear(); nodeList = node.elementsByTagName("ACCOUNTIDS"); if (nodeList.count() > 0) { nodeList = nodeList.item(0).toElement().elementsByTagName("ACCOUNTID"); for (int i = 0; i < nodeList.count(); ++i) { m_accountList << nodeList.item(i).toElement().attribute("id"); } } }
void SummaryWizardPage::initializePage() { // General if (field("borrowButton").toBool()) m_summaryLoanType->setText(i18n("borrowed")); else m_summaryLoanType->setText(i18n("lend")); m_summaryFirstPayment->setText(KGlobal::locale()->formatDate(field("firstDueDateEdit").toDate())); const QString &payeeId = field("payeeEdit").toString(); if (!payeeId.isEmpty()) { try { const MyMoneyPayee &payee = MyMoneyFile::instance()->payee(payeeId); m_summaryPayee->setText(payee.name()); } catch (const MyMoneyException &) { qWarning("Unable to load the payee name from the id"); } } else { m_summaryPayee->setText(i18n("not assigned")); } // Calculation if (field("interestOnReceptionButton").toBool()) m_summaryInterestDue->setText(i18n("on reception")); else m_summaryInterestDue->setText(i18n("on due date")); m_summaryPaymentFrequency->setText(MyMoneySchedule::occurrenceToString(MyMoneySchedule::occurrenceE(field("paymentFrequencyUnitEdit").toInt()))); m_summaryAmount->setText(field("loanAmount6").toString()); m_summaryInterestRate->setText(field("interestRate6").toString()); m_summaryTerm->setText(field("duration6").toString()); m_summaryPeriodicPayment->setText(field("payment6").toString()); m_summaryBalloonPayment->setText(field("balloon6").toString()); // Payment try { QStringList sel = field("interestAccountEdit").toStringList(); if (sel.count() != 1) throw MYMONEYEXCEPTION("Need a single selected interest category"); MyMoneyAccount acc = MyMoneyFile::instance()->account(sel.first()); m_summaryInterestCategory->setText(acc.name()); } catch (const MyMoneyException &) { qWarning("Unable to determine interest category for loan account creation"); } m_summaryAdditionalFees->setText(field("additionalCost").toString()); m_summaryTotalPeriodicPayment->setText(field("periodicPayment").toString()); m_summaryNextPayment->setText(KGlobal::locale()->formatDate(field("nextDueDateEdit").toDate())); try { QStringList sel = field("paymentAccountEdit").toStringList(); if (sel.count() != 1) throw MYMONEYEXCEPTION("Need a single selected payment account"); MyMoneyAccount acc = MyMoneyFile::instance()->account(sel.first()); m_summaryPaymentAccount->setText(acc.name()); } catch (const MyMoneyException &) { qWarning("Unable to determine payment account for loan account creation"); } }
MyMoneySplit::MyMoneySplit(const QDomElement& node) : MyMoneyObject(node, false), MyMoneyKeyValueContainer(node.elementsByTagName("KEYVALUEPAIRS").item(0).toElement()) { if ("SPLIT" != node.tagName()) throw MYMONEYEXCEPTION("Node was not SPLIT"); clearId(); m_payee = QStringEmpty(node.attribute("payee")); QDomNodeList nodeList = node.elementsByTagName("TAG"); for (int i = 0; i < nodeList.count(); i++) m_tagList << QStringEmpty(nodeList.item(i).toElement().attribute("id")); m_reconcileDate = stringToDate(QStringEmpty(node.attribute("reconciledate"))); m_action = QStringEmpty(node.attribute("action")); m_reconcileFlag = static_cast<MyMoneySplit::reconcileFlagE>(node.attribute("reconcileflag").toInt()); m_memo = QStringEmpty(node.attribute("memo")); m_value = MyMoneyMoney(QStringEmpty(node.attribute("value"))); m_shares = MyMoneyMoney(QStringEmpty(node.attribute("shares"))); m_price = MyMoneyMoney(QStringEmpty(node.attribute("price"))); m_account = QStringEmpty(node.attribute("account")); m_number = QStringEmpty(node.attribute("number")); m_bankID = QStringEmpty(node.attribute("bankid")); }
MyMoneyKeyValueContainer::MyMoneyKeyValueContainer(const QDomElement& node) { if (!node.isNull()) { if ("KEYVALUEPAIRS" != node.tagName()) throw MYMONEYEXCEPTION("Node was not KEYVALUEPAIRS"); m_kvp.clear(); QDomNodeList nodeList = node.elementsByTagName("PAIR"); for (int i = 0; i < nodeList.count(); ++i) { const QDomElement& el(nodeList.item(i).toElement()); m_kvp[el.attribute("key")] = el.attribute("value"); } } }
MyMoneyTag::MyMoneyTag(const QDomElement& node) : MyMoneyObject(node) { if ("TAG" != node.tagName()) { throw MYMONEYEXCEPTION("Node was not TAG"); } m_name = node.attribute("name"); if (node.hasAttribute("tagcolor")) { m_tag_color.setNamedColor(node.attribute("tagcolor")); } if (node.hasAttribute("notes")) { m_notes = node.attribute("notes"); } m_closed = node.attribute("closed", "0").toUInt(); }
bool MyMoneyTemplate::saveTemplate(const KUrl& url) { QString filename; if (!url.isValid()) { qDebug("Invalid template URL '%s'", qPrintable(url.url())); return false; } if (url.isLocalFile()) { filename = url.toLocalFile(); KSaveFile qfile(filename/*, 0600*/); if (qfile.open()) { saveToLocalFile(&qfile); if (!qfile.finalize()) { throw MYMONEYEXCEPTION(i18n("Unable to write changes to '%1'", filename)); } } else { throw MYMONEYEXCEPTION(i18n("Unable to write changes to '%1'", filename)); } } else { KTemporaryFile tmpfile; KSaveFile qfile(tmpfile.fileName()); if (qfile.open()) { saveToLocalFile(&qfile); if (!qfile.finalize()) { throw MYMONEYEXCEPTION(i18n("Unable to upload to '%1'", url.url())); } } else { throw MYMONEYEXCEPTION(i18n("Unable to upload to '%1'", url.url())); } if (!KIO::NetAccess::upload(tmpfile.fileName(), url, 0)) throw MYMONEYEXCEPTION(i18n("Unable to upload to '%1'", url.url())); } return true; }
// Function to read in the file, send to XML parser. void MyMoneyStorageXML::readFile(QIODevice* pDevice, IMyMoneySerialize* storage) { Q_CHECK_PTR(storage); Q_CHECK_PTR(pDevice); if (!storage) return; m_storage = storage; m_doc = new QDomDocument; Q_CHECK_PTR(m_doc); qDebug("reading file"); // creating the QXmlInputSource object based on a QIODevice object // reads all data of the underlying object into memory. I have not // found an object that reads on the fly. I tried to derive one myself, // but there could be a severe problem with decoding when reading // blocks of data and not a stream. So I left it the way it is. (ipwizard) QXmlInputSource xml(pDevice); qDebug("start parsing file"); MyMoneyXmlContentHandler mmxml(this); QXmlSimpleReader reader; reader.setContentHandler(&mmxml); if (!reader.parse(&xml, false)) { delete m_doc; m_doc = 0; signalProgress(-1, -1); throw MYMONEYEXCEPTION("File was not parsable!"); } // check if we need to build up the account balances if (fileVersionRead < 2) m_storage->rebuildAccountBalances(); delete m_doc; m_doc = 0; // this seems to be nonsense, but it clears the dirty flag // as a side-effect. m_storage->setLastModificationDate(m_storage->lastModificationDate()); m_storage = 0; //hides the progress bar. signalProgress(-1, -1); }
void TransactionMatcher::match(MyMoneyTransaction tm, MyMoneySplit sm, MyMoneyTransaction ti, MyMoneySplit si, bool allowImportedTransactions) { const MyMoneySecurity& sec = MyMoneyFile::instance()->security(m_account.currencyId()); // Now match the transactions. // // 'Matching' the transactions entails DELETING the end transaction, // and MODIFYING the start transaction as needed. // // There are a variety of ways that a transaction can conflict. // Post date, splits, amount are the ones that seem to matter. // TODO: Handle these conflicts intelligently, at least warning // the user, or better yet letting the user choose which to use. // // For now, we will just use the transaction details from the start // transaction. The only thing we'll take from the end transaction // are the bank ID's. // // What we have to do here is iterate over the splits in the end // transaction, and find the corresponding split in the start // transaction. If there is a bankID in the end split but not the // start split, add it to the start split. If there is a bankID // in BOTH, then this transaction cannot be merged (both transactions // were imported!!) If the corresponding start split cannot be // found and the end split has a bankID, we should probably just fail. // Although we could ADD it to the transaction. // ipwizard: Don't know if iterating over the transactions is a good idea. // In case of a split transaction recorded with KMyMoney and the transaction // data being imported consisting only of a single category assignment, this // does not make much sense. The same applies for investment transactions // stored in KMyMoney against imported transactions. I think a better solution // is to just base the match on the splits referencing the same (currently // selected) account. // verify, that tm is a manual (non-matched) transaction // allow matching two manual transactions if ((!allowImportedTransactions && tm.isImported()) || sm.isMatched()) throw MYMONEYEXCEPTION(i18n("First transaction does not match requirement for matching")); // verify that the amounts are the same, otherwise we should not be matching! if (sm.shares() != si.shares()) { throw MYMONEYEXCEPTION(i18n("Splits for %1 have conflicting values (%2,%3)", m_account.name(), MyMoneyUtils::formatMoney(sm.shares(), m_account, sec), MyMoneyUtils::formatMoney(si.shares(), m_account, sec))); } // check that dates are within user's setting const int gap = abs(tm.postDate().toJulianDay() - ti.postDate().toJulianDay()); if (gap > KMyMoneyGlobalSettings::matchInterval()) { int rc = KMessageBox::questionYesNo(0, i18np("The transaction dates are one day apart. Do you want to match them anyway?", "The transaction dates are %1 days apart. Do you want to match them anyway?", gap)); if (rc == KMessageBox::No) { return; } } // ipwizard: I took over the code to keep the bank id found in the endMatchTransaction // This might not work for QIF imports as they don't setup this information. It sure // makes sense for OFX and HBCI. const QString& bankID = si.bankID(); if (!bankID.isEmpty()) { try { if (sm.bankID().isEmpty()) { sm.setBankID(bankID); tm.modifySplit(sm); } } catch (const MyMoneyException &e) { QString estr = e.what(); throw MYMONEYEXCEPTION(i18n("Unable to match all splits (%1)", estr)); } } // // we now allow matching of two non-imported transactions // // mark the split as cleared if it does not have a reconciliation information yet if (sm.reconcileFlag() == MyMoneySplit::NotReconciled) { sm.setReconcileFlag(MyMoneySplit::Cleared); } // if we don't have a payee assigned to the manually entered transaction // we use the one we found in the imported transaction if (sm.payeeId().isEmpty() && !si.payeeId().isEmpty()) { sm.setValue("kmm-orig-payee", sm.payeeId()); sm.setPayeeId(si.payeeId()); } // We use the imported postdate and keep the previous one for unmatch if (tm.postDate() != ti.postDate()) { sm.setValue("kmm-orig-postdate", tm.postDate().toString(Qt::ISODate)); tm.setPostDate(ti.postDate()); } // combine the two memos into one QString memo = sm.memo(); if (!si.memo().isEmpty() && si.memo() != memo) { sm.setValue("kmm-orig-memo", memo); if (!memo.isEmpty()) memo += '\n'; memo += si.memo(); } sm.setMemo(memo); // remember the split we matched sm.setValue("kmm-match-split", si.id()); sm.addMatch(ti); tm.modifySplit(sm); ti.modifySplit(si);/// MyMoneyFile::instance()->modifyTransaction(tm); // Delete the end transaction if it was stored in the engine if (!ti.id().isEmpty()) MyMoneyFile::instance()->removeTransaction(ti); }
MyMoneyReport::MyMoneyReport(ERowType _rt, unsigned _ct, dateOptionE _dl, EDetailLevel _ss, const QString& _name, const QString& _comment) : m_name(_name), m_comment(_comment), m_detailLevel(_ss), m_convertCurrency(true), m_favorite(false), m_tax(false), m_investments(false), m_loans(false), m_reportType(kTypeArray[_rt]), m_rowType(_rt), m_columnsAreDays(false), m_queryColumns(eQCnone), m_dateLock(_dl), m_accountGroupFilter(false), m_chartType(eChartLine), m_chartDataLabels(true), m_chartGridLines(true), m_chartByDefault(false), m_includeSchedules(false), m_includeTransfers(false), m_includeBudgetActuals(false), m_includeUnusedAccounts(false), m_showRowTotals(false), m_includeForecast(false), m_includeMovingAverage(false), m_movingAverageDays(0), m_includePrice(false), m_includeAveragePrice(false), m_mixedTime(false), m_currentDateColumn(0), m_skipZero(false) { //set initial values m_chartLineWidth = m_lineWidth; //set report type if (m_reportType == ePivotTable) m_columnType = static_cast<EColumnType>(_ct); if (m_reportType == eQueryTable) m_queryColumns = static_cast<EQueryColumns>(_ct); setDateFilter(_dl); //throw exception if the type is inconsistent if ((_rt > static_cast<ERowType>(sizeof(kTypeArray) / sizeof(kTypeArray[0]))) || (m_reportType == eNoReport)) throw MYMONEYEXCEPTION("Invalid report type"); //add the corresponding account groups if (_rt == MyMoneyReport::eAssetLiability) { addAccountGroup(MyMoneyAccount::Asset); addAccountGroup(MyMoneyAccount::Liability); m_showRowTotals = true; } if (_rt == MyMoneyReport::eExpenseIncome) { addAccountGroup(MyMoneyAccount::Expense); addAccountGroup(MyMoneyAccount::Income); m_showRowTotals = true; } //FIXME take this out once we have sorted out all issues regarding budget of assets and liabilities -- [email protected] if (_rt == MyMoneyReport::eBudget || _rt == MyMoneyReport::eBudgetActual) { addAccountGroup(MyMoneyAccount::Expense); addAccountGroup(MyMoneyAccount::Income); } if (_rt == MyMoneyReport::eAccountInfo) { addAccountGroup(MyMoneyAccount::Asset); addAccountGroup(MyMoneyAccount::Liability); } //cash flow reports show splits for all account groups if (_rt == MyMoneyReport::eCashFlow) { addAccountGroup(MyMoneyAccount::Expense); addAccountGroup(MyMoneyAccount::Income); addAccountGroup(MyMoneyAccount::Asset); addAccountGroup(MyMoneyAccount::Liability); } }
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; }