summaryrefslogtreecommitdiffstats
path: root/kmymoney2/mymoney
diff options
context:
space:
mode:
Diffstat (limited to 'kmymoney2/mymoney')
-rw-r--r--kmymoney2/mymoney/Makefile.am50
-rw-r--r--kmymoney2/mymoney/autotest.h24
-rw-r--r--kmymoney2/mymoney/mymoneyaccount.cpp744
-rw-r--r--kmymoney2/mymoney/mymoneyaccount.h692
-rw-r--r--kmymoney2/mymoney/mymoneyaccounttest.cpp589
-rw-r--r--kmymoney2/mymoney/mymoneyaccounttest.h70
-rw-r--r--kmymoney2/mymoney/mymoneybudget.cpp354
-rw-r--r--kmymoney2/mymoney/mymoneybudget.h269
-rw-r--r--kmymoney2/mymoney/mymoneycategory.cpp170
-rw-r--r--kmymoney2/mymoney/mymoneycategory.h68
-rw-r--r--kmymoney2/mymoney/mymoneyexception.cpp35
-rw-r--r--kmymoney2/mymoney/mymoneyexception.h115
-rw-r--r--kmymoney2/mymoney/mymoneyexceptiontest.cpp51
-rw-r--r--kmymoney2/mymoney/mymoneyexceptiontest.h47
-rw-r--r--kmymoney2/mymoney/mymoneyfile.cpp2332
-rw-r--r--kmymoney2/mymoney/mymoneyfile.h1470
-rw-r--r--kmymoney2/mymoney/mymoneyfiletest.cpp1550
-rw-r--r--kmymoney2/mymoney/mymoneyfiletest.h128
-rw-r--r--kmymoney2/mymoney/mymoneyfinancialcalculator.cpp348
-rw-r--r--kmymoney2/mymoney/mymoneyfinancialcalculator.h317
-rw-r--r--kmymoney2/mymoney/mymoneyfinancialcalculatortest.cpp189
-rw-r--r--kmymoney2/mymoney/mymoneyfinancialcalculatortest.h72
-rw-r--r--kmymoney2/mymoney/mymoneyforecast.cpp1340
-rw-r--r--kmymoney2/mymoney/mymoneyforecast.h408
-rw-r--r--kmymoney2/mymoney/mymoneyforecasttest.cpp977
-rw-r--r--kmymoney2/mymoney/mymoneyforecasttest.h96
-rw-r--r--kmymoney2/mymoney/mymoneyinstitution.cpp182
-rw-r--r--kmymoney2/mymoney/mymoneyinstitution.h206
-rw-r--r--kmymoney2/mymoney/mymoneyinstitutiontest.cpp296
-rw-r--r--kmymoney2/mymoney/mymoneyinstitutiontest.h61
-rw-r--r--kmymoney2/mymoney/mymoneyinvesttransaction.cpp42
-rw-r--r--kmymoney2/mymoney/mymoneyinvesttransaction.h43
-rw-r--r--kmymoney2/mymoney/mymoneykeyvaluecontainer.cpp120
-rw-r--r--kmymoney2/mymoney/mymoneykeyvaluecontainer.h139
-rw-r--r--kmymoney2/mymoney/mymoneykeyvaluecontainertest.cpp189
-rw-r--r--kmymoney2/mymoney/mymoneykeyvaluecontainertest.h61
-rw-r--r--kmymoney2/mymoney/mymoneymoney.cpp794
-rw-r--r--kmymoney2/mymoney/mymoneymoney.h612
-rw-r--r--kmymoney2/mymoney/mymoneymoneytest.cpp591
-rw-r--r--kmymoney2/mymoney/mymoneymoneytest.h99
-rw-r--r--kmymoney2/mymoney/mymoneyobject.cpp74
-rw-r--r--kmymoney2/mymoney/mymoneyobject.h129
-rw-r--r--kmymoney2/mymoney/mymoneyobjectcontainer.cpp218
-rw-r--r--kmymoney2/mymoney/mymoneyobjectcontainer.h99
-rw-r--r--kmymoney2/mymoney/mymoneyobjecttest.cpp73
-rw-r--r--kmymoney2/mymoney/mymoneyobjecttest.h50
-rw-r--r--kmymoney2/mymoney/mymoneyobserver.cpp30
-rw-r--r--kmymoney2/mymoney/mymoneyobserver.h55
-rw-r--r--kmymoney2/mymoney/mymoneyobservertest.h106
-rw-r--r--kmymoney2/mymoney/mymoneypayee.cpp220
-rw-r--r--kmymoney2/mymoney/mymoneypayee.h206
-rw-r--r--kmymoney2/mymoney/mymoneypayeetest.cpp76
-rw-r--r--kmymoney2/mymoney/mymoneypayeetest.h41
-rw-r--r--kmymoney2/mymoney/mymoneyprice.cpp113
-rw-r--r--kmymoney2/mymoney/mymoneyprice.h158
-rw-r--r--kmymoney2/mymoney/mymoneypricetest.cpp90
-rw-r--r--kmymoney2/mymoney/mymoneypricetest.h48
-rw-r--r--kmymoney2/mymoney/mymoneyreport.cpp787
-rw-r--r--kmymoney2/mymoney/mymoneyreport.h497
-rw-r--r--kmymoney2/mymoney/mymoneyscheduled.cpp1372
-rw-r--r--kmymoney2/mymoney/mymoneyscheduled.h695
-rw-r--r--kmymoney2/mymoney/mymoneyscheduletest.cpp1909
-rw-r--r--kmymoney2/mymoney/mymoneyscheduletest.h106
-rw-r--r--kmymoney2/mymoney/mymoneysecurity.cpp180
-rw-r--r--kmymoney2/mymoney/mymoneysecurity.h150
-rw-r--r--kmymoney2/mymoney/mymoneysecuritytest.cpp210
-rw-r--r--kmymoney2/mymoney/mymoneysecuritytest.h58
-rw-r--r--kmymoney2/mymoney/mymoneysplit.cpp272
-rw-r--r--kmymoney2/mymoney/mymoneysplit.h307
-rw-r--r--kmymoney2/mymoney/mymoneysplittest.cpp306
-rw-r--r--kmymoney2/mymoney/mymoneysplittest.h67
-rw-r--r--kmymoney2/mymoney/mymoneystatement.cpp299
-rw-r--r--kmymoney2/mymoney/mymoneystatement.h145
-rw-r--r--kmymoney2/mymoney/mymoneysubject.cpp58
-rw-r--r--kmymoney2/mymoney/mymoneysubject.h59
-rw-r--r--kmymoney2/mymoney/mymoneytransaction.cpp484
-rw-r--r--kmymoney2/mymoney/mymoneytransaction.h353
-rw-r--r--kmymoney2/mymoney/mymoneytransactionfilter.cpp860
-rw-r--r--kmymoney2/mymoney/mymoneytransactionfilter.h578
-rw-r--r--kmymoney2/mymoney/mymoneytransactiontest.cpp628
-rw-r--r--kmymoney2/mymoney/mymoneytransactiontest.h96
-rw-r--r--kmymoney2/mymoney/mymoneyutils.cpp339
-rw-r--r--kmymoney2/mymoney/mymoneyutils.h192
-rw-r--r--kmymoney2/mymoney/storage/Makefile.am20
-rw-r--r--kmymoney2/mymoney/storage/imymoneyserialize.cpp31
-rw-r--r--kmymoney2/mymoney/storage/imymoneyserialize.h374
-rw-r--r--kmymoney2/mymoney/storage/imymoneystorage.cpp38
-rw-r--r--kmymoney2/mymoney/storage/imymoneystorage.h886
-rw-r--r--kmymoney2/mymoney/storage/imymoneystorageformat.cpp31
-rw-r--r--kmymoney2/mymoney/storage/imymoneystorageformat.h75
-rw-r--r--kmymoney2/mymoney/storage/mymoneydatabasemgr.cpp1880
-rw-r--r--kmymoney2/mymoney/storage/mymoneydatabasemgr.h1038
-rw-r--r--kmymoney2/mymoney/storage/mymoneydatabasemgrtest.cpp1996
-rw-r--r--kmymoney2/mymoney/storage/mymoneydatabasemgrtest.h143
-rw-r--r--kmymoney2/mymoney/storage/mymoneymap.h328
-rw-r--r--kmymoney2/mymoney/storage/mymoneymaptest.cpp38
-rw-r--r--kmymoney2/mymoney/storage/mymoneymaptest.h47
-rw-r--r--kmymoney2/mymoney/storage/mymoneyseqaccessmgr.cpp1944
-rw-r--r--kmymoney2/mymoney/storage/mymoneyseqaccessmgr.h1232
-rw-r--r--kmymoney2/mymoney/storage/mymoneyseqaccessmgrtest.cpp1705
-rw-r--r--kmymoney2/mymoney/storage/mymoneyseqaccessmgrtest.h131
-rw-r--r--kmymoney2/mymoney/storage/mymoneystorageanon.cpp294
-rw-r--r--kmymoney2/mymoney/storage/mymoneystorageanon.h113
-rw-r--r--kmymoney2/mymoney/storage/mymoneystoragebin.h49
-rw-r--r--kmymoney2/mymoney/storage/mymoneystoragedump.cpp446
-rw-r--r--kmymoney2/mymoney/storage/mymoneystoragedump.h56
-rw-r--r--kmymoney2/mymoney/storage/mymoneystoragesql.cpp4511
-rw-r--r--kmymoney2/mymoney/storage/mymoneystoragesql.h807
-rw-r--r--kmymoney2/mymoney/storage/mymoneystoragexml.cpp908
-rw-r--r--kmymoney2/mymoney/storage/mymoneystoragexml.h156
110 files changed, 48010 insertions, 0 deletions
diff --git a/kmymoney2/mymoney/Makefile.am b/kmymoney2/mymoney/Makefile.am
new file mode 100644
index 0000000..84ec924
--- /dev/null
+++ b/kmymoney2/mymoney/Makefile.am
@@ -0,0 +1,50 @@
+KDE_OPTIONS = noautodist
+
+#
+# The LIBVERSION setting controls the libtool versioning system for shared
+# libraries. It is constructed out of the triplet CURRENT:REVISION:AGE.
+#
+# Here are the rules on howto update the version info:
+#
+# 1. Update the version information only immediately before a public
+# release of your software. More frequent updates are unnecessary,
+# and only guarantee that the current interface number gets larger
+# faster.
+#
+# 2. If the library source code has changed at all since the last
+# update, then increment REVISION (`C:R:A' becomes `C:r+1:A').
+#
+# 3. If any interfaces have been added, removed, or changed since the
+# last update, increment CURRENT, and set REVISION to 0.
+#
+# 4. If any interfaces have been added since the last public release,
+# then increment AGE.
+#
+# 5. If any interfaces have been removed since the last public release,
+# then set AGE to 0.
+#
+# The above information is copied from 'info libtool'.
+
+LIBVERSION=5:0:0
+
+INCLUDES = $(all_includes) -I$(top_srcdir) -I.
+lib_LTLIBRARIES = libkmm_mymoney.la
+
+libkmm_mymoney_la_METASOURCES = AUTO
+
+libkmm_mymoney_la_SOURCES = mymoneymoney.cpp mymoneyfinancialcalculator.cpp mymoneytransactionfilter.cpp mymoneyobject.cpp mymoneykeyvaluecontainer.cpp mymoneyobserver.cpp mymoneysubject.cpp mymoneysplit.cpp mymoneyinstitution.cpp mymoneyexception.cpp mymoneyinvesttransaction.cpp mymoneyutils.cpp mymoneysecurity.cpp mymoneytransaction.cpp mymoneyscheduled.cpp mymoneypayee.cpp mymoneyfile.cpp mymoneycategory.cpp mymoneyaccount.cpp mymoneyreport.cpp mymoneystatement.cpp mymoneyprice.cpp mymoneybudget.cpp mymoneyobjectcontainer.cpp mymoneyforecast.cpp
+libkmm_mymoney_la_LDFLAGS = $(all_libraries) -version-info $(LIBVERSION)
+
+SUBDIRS = storage
+
+instdir=$(includedir)/kmymoney
+inst_HEADERS = mymoneyobject.h mymoneyaccount.h mymoneycategory.h mymoneyexception.h mymoneyfile.h mymoneyfinancialcalculator.h mymoneyinstitution.h mymoneyinvesttransaction.h mymoneykeyvaluecontainer.h mymoneymoney.h mymoneyobserver.h mymoneypayee.h mymoneyprice.h mymoneyreport.h mymoneyscheduled.h mymoneysecurity.h mymoneysplit.h mymoneystatement.h mymoneysubject.h mymoneytransactionfilter.h mymoneytransaction.h mymoneyutils.h mymoneybudget.h mymoneyobjectcontainer.h mymoneyforecast.h
+
+noinst_HEADERS = autotest.h mymoneyaccounttest.h mymoneyfinancialcalculatortest.h mymoneykeyvaluecontainertest.h mymoneyexceptiontest.h mymoneyfiletest.h mymoneyinstitutiontest.h mymoneymoneytest.h mymoneyobservertest.h mymoneyscheduletest.h mymoneysplittest.h mymoneysecuritytest.h mymoneytransactiontest.h mymoneypricetest.h mymoneyobjecttest.h mymoneyforecasttest.h mymoneypayeetest.h
+
+if CPPUNIT
+check_LIBRARIES = libmymoneytest.a
+
+libmymoneytest_a_SOURCES = mymoneytransactiontest.cpp mymoneysplittest.cpp mymoneymoneytest.cpp mymoneyfiletest.cpp mymoneyaccounttest.cpp mymoneyexceptiontest.cpp mymoneyinstitutiontest.cpp mymoneykeyvaluecontainertest.cpp mymoneyscheduletest.cpp mymoneyfinancialcalculatortest.cpp mymoneysecuritytest.cpp mymoneypricetest.cpp mymoneyobjecttest.cpp mymoneyforecasttest.cpp mymoneypayeetest.cpp
+endif
+
diff --git a/kmymoney2/mymoney/autotest.h b/kmymoney2/mymoney/autotest.h
new file mode 100644
index 0000000..31b372f
--- /dev/null
+++ b/kmymoney2/mymoney/autotest.h
@@ -0,0 +1,24 @@
+/***************************************************************************
+ autotest.h
+ -------------------
+ copyright : (C) 2002 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 __AUTOTEST_H
+#define __AUTOTEST_H
+
+#include "mymoneyexception.h"
+
+void unexpectedException(MyMoneyException *e);
+
+#endif // __AUTOTEST_H
diff --git a/kmymoney2/mymoney/mymoneyaccount.cpp b/kmymoney2/mymoney/mymoneyaccount.cpp
new file mode 100644
index 0000000..8c304cd
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyaccount.cpp
@@ -0,0 +1,744 @@
+/***************************************************************************
+ mymoneyaccount.cpp
+ -------------------
+ copyright : (C) 2000 by Michael Edwardes
+ (C) 2002 by Thomas Baumagrt
+ email : mte@users.sourceforge.net
+ ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qregexp.h>
+#include <kstandarddirs.h>
+#include <kiconloader.h>
+
+// ----------------------------------------------------------------------------
+// KDE Includes
+
+#include <klocale.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include <kmymoney/mymoneyexception.h>
+#include <kmymoney/mymoneyaccount.h>
+#include <kmymoney/mymoneysplit.h>
+
+MyMoneyAccount::MyMoneyAccount() :
+ m_fraction(-1)
+{
+ m_accountType = UnknownAccountType;
+}
+
+MyMoneyAccount::~MyMoneyAccount()
+{
+}
+
+MyMoneyAccount::MyMoneyAccount(const QString& id, const MyMoneyAccount& right) :
+ MyMoneyObject(id)
+{
+ *this = right;
+ setId(id);
+}
+
+MyMoneyAccount::MyMoneyAccount(const QDomElement& node) :
+ MyMoneyObject(node),
+ MyMoneyKeyValueContainer(node.elementsByTagName("KEYVALUEPAIRS").item(0).toElement()),
+ m_fraction(-1)
+{
+ if("ACCOUNT" != node.tagName())
+ throw new MYMONEYEXCEPTION("Node was not ACCOUNT");
+
+ setName(node.attribute("name"));
+
+ // qDebug("Reading information for account %s", acc.name().data());
+
+ setParentAccountId(QStringEmpty(node.attribute("parentaccount")));
+ setLastModified(stringToDate(QStringEmpty(node.attribute("lastmodified"))));
+ setLastReconciliationDate(stringToDate(QStringEmpty(node.attribute("lastreconciled"))));
+
+ if(!m_lastReconciliationDate.isValid()) {
+ // for some reason, I was unable to access our own kvp at this point through
+ // the value() method. It always returned empty strings. The workaround for
+ // this is to construct a local kvp the same way as we have done before and
+ // extract the value from it.
+ //
+ // Since we want to get rid of the lastStatementDate record anyway, this seems
+ // to be ok for now. (ipwizard - 2008-08-14)
+ QString txt = MyMoneyKeyValueContainer(node.elementsByTagName("KEYVALUEPAIRS").item(0).toElement()).value("lastStatementDate");
+ if(!txt.isEmpty()) {
+ setLastReconciliationDate(QDate::fromString(txt, Qt::ISODate));
+ }
+ }
+
+ setInstitutionId(QStringEmpty(node.attribute("institution")));
+ setNumber(QStringEmpty(node.attribute("number")));
+ setOpeningDate(stringToDate(QStringEmpty(node.attribute("opened"))));
+ setCurrencyId(QStringEmpty(node.attribute("currency")));
+
+ QString tmp = QStringEmpty(node.attribute("type"));
+ bool bOK = false;
+ int type = tmp.toInt(&bOK);
+ if(bOK) {
+ setAccountType(static_cast<MyMoneyAccount::accountTypeE>(type));
+ } else {
+ qWarning("XMLREADER: Account %s had invalid or no account type information.", name().data());
+ }
+
+ if(node.hasAttribute("openingbalance")) {
+ if(!MyMoneyMoney(node.attribute("openingbalance")).isZero()) {
+ QString msg = i18n("Account %1 contains an opening balance. Please use a KMyMoney version >= 0.8 and < 0.9 to correct the problem.").arg(m_name);
+ throw new MYMONEYEXCEPTION(msg);
+ }
+ }
+ setDescription(node.attribute("description"));
+
+ m_id = QStringEmpty(node.attribute("id"));
+ // qDebug("Account %s has id of %s, type of %d, parent is %s.", acc.name().data(), id.data(), type, acc.parentAccountId().data());
+
+ // Process any Sub-Account information found inside the account entry.
+ m_accountList.clear();
+ QDomNodeList nodeList = node.elementsByTagName("SUBACCOUNTS");
+ if(nodeList.count() > 0) {
+ nodeList = nodeList.item(0).toElement().elementsByTagName("SUBACCOUNT");
+ for(unsigned int i = 0; i < nodeList.count(); ++i) {
+ addAccountId(QString(nodeList.item(i).toElement().attribute("id")));
+ }
+ }
+
+ nodeList = node.elementsByTagName("ONLINEBANKING");
+ if(nodeList.count() > 0) {
+ QDomNamedNodeMap attributes = nodeList.item(0).toElement().attributes();
+ for(unsigned int i = 0; i < attributes.count(); ++i) {
+ const QDomAttr& it_attr = attributes.item(i).toAttr();
+ m_onlineBankingSettings.setValue(it_attr.name().utf8(), it_attr.value());
+ }
+ }
+
+}
+
+void MyMoneyAccount::setName(const QString& name)
+{
+ m_name = name;
+}
+
+void MyMoneyAccount::setNumber(const QString& number)
+{
+ m_number = number;
+}
+
+void MyMoneyAccount::setDescription(const QString& desc)
+{
+ m_description = desc;
+}
+
+void MyMoneyAccount::setInstitutionId(const QString& id)
+{
+ m_institution = id;
+}
+
+void MyMoneyAccount::setLastModified(const QDate& date)
+{
+ m_lastModified = date;
+}
+
+void MyMoneyAccount::setOpeningDate(const QDate& date)
+{
+ m_openingDate = date;
+}
+
+void MyMoneyAccount::setLastReconciliationDate(const QDate& date)
+{
+ // FIXME: for a limited time (maybe until we delivered 1.0) we
+ // keep the last reconciliation date also in the KVP for backward
+ // compatability. After that, the setValue() statemetn should be removed
+ // and the XML ctor should remove the value completely from the KVP
+ setValue("lastStatementDate", date.toString(Qt::ISODate));
+ m_lastReconciliationDate = date;
+}
+
+void MyMoneyAccount::setParentAccountId(const QString& parent)
+{
+ m_parentAccount = parent;
+}
+
+void MyMoneyAccount::setAccountType(const accountTypeE type)
+{
+ m_accountType = type;
+}
+
+void MyMoneyAccount::addAccountId(const QString& account)
+{
+ if(!m_accountList.contains(account))
+ m_accountList += account;
+}
+
+void MyMoneyAccount::removeAccountIds(void)
+{
+ m_accountList.clear();
+}
+
+void MyMoneyAccount::removeAccountId(const QString& account)
+{
+ QStringList::Iterator it;
+
+ it = m_accountList.find(account);
+ if(it != m_accountList.end())
+ m_accountList.remove(it);
+}
+
+MyMoneyAccount::accountTypeE MyMoneyAccount::accountGroup(MyMoneyAccount::accountTypeE type)
+{
+ switch(type) {
+ case MyMoneyAccount::Checkings:
+ case MyMoneyAccount::Savings:
+ case MyMoneyAccount::Cash:
+ case MyMoneyAccount::Currency:
+ case MyMoneyAccount::Investment:
+ case MyMoneyAccount::MoneyMarket:
+ case MyMoneyAccount::CertificateDep:
+ case MyMoneyAccount::AssetLoan:
+ case MyMoneyAccount::Stock:
+ return MyMoneyAccount::Asset;
+
+ case MyMoneyAccount::CreditCard:
+ case MyMoneyAccount::Loan:
+ return MyMoneyAccount::Liability;
+
+ default:
+ return type;
+ }
+}
+
+bool MyMoneyAccount::operator == (const MyMoneyAccount& right) const
+{
+ return (MyMoneyKeyValueContainer::operator==(right) &&
+ MyMoneyObject::operator==(right) &&
+ (m_accountList == right.m_accountList) &&
+ (m_accountType == right.m_accountType) &&
+ (m_lastModified == right.m_lastModified) &&
+ (m_lastReconciliationDate == right.m_lastReconciliationDate) &&
+ ((m_name.length() == 0 && right.m_name.length() == 0) || (m_name == right.m_name)) &&
+ ((m_number.length() == 0 && right.m_number.length() == 0) || (m_number == right.m_number)) &&
+ ((m_description.length() == 0 && right.m_description.length() == 0) || (m_description == right.m_description)) &&
+ (m_openingDate == right.m_openingDate) &&
+ (m_parentAccount == right.m_parentAccount) &&
+ (m_currencyId == right.m_currencyId) &&
+ (m_institution == right.m_institution) );
+}
+
+MyMoneyAccount::accountTypeE MyMoneyAccount::accountGroup(void) const
+{
+ return accountGroup(m_accountType);
+}
+
+void MyMoneyAccount::setCurrencyId(const QString& id)
+{
+ m_currencyId = id;
+}
+
+bool MyMoneyAccount::isAssetLiability(void) const
+{
+ return accountGroup() == Asset || accountGroup() == Liability;
+}
+
+bool MyMoneyAccount::isIncomeExpense(void) const
+{
+ return accountGroup() == Income || accountGroup() == Expense;
+}
+
+bool MyMoneyAccount::isLoan(void) const
+{
+ return accountType() == Loan || accountType() == AssetLoan;
+}
+
+bool MyMoneyAccount::isInvest(void) const
+{
+ return accountType() == Stock;
+}
+
+
+MyMoneyAccountLoan::MyMoneyAccountLoan(const MyMoneyAccount& acc)
+ : MyMoneyAccount(acc)
+{
+}
+
+const MyMoneyMoney MyMoneyAccountLoan::loanAmount(void) const
+{
+ return MyMoneyMoney(value("loan-amount"));
+}
+
+void MyMoneyAccountLoan::setLoanAmount(const MyMoneyMoney& amount)
+{
+ setValue("loan-amount", amount.toString());
+}
+
+const MyMoneyMoney MyMoneyAccountLoan::interestRate(const QDate& date) const
+{
+ MyMoneyMoney rate;
+ QString key;
+ QString val;
+
+ if(!date.isValid())
+ return rate;
+
+ key.sprintf("ir-%04d-%02d-%02d", date.year(), date.month(), date.day());
+
+ QRegExp regExp("ir-(\\d{4})-(\\d{2})-(\\d{2})");
+
+ QMap<QString, QString>::ConstIterator it;
+
+ for(it = pairs().begin(); it != pairs().end(); ++it) {
+ if(regExp.search(it.key()) > -1) {
+ if(qstrcmp(it.key(),key) <= 0)
+ val = *it;
+ else
+ break;
+
+ } else if(!val.isEmpty())
+ break;
+ }
+
+ if(!val.isEmpty()) {
+ rate = MyMoneyMoney(val);
+ }
+
+ return rate;
+}
+
+void MyMoneyAccountLoan::setInterestRate(const QDate& date, const MyMoneyMoney& value)
+{
+ if(!date.isValid())
+ return;
+
+ QString key;
+ key.sprintf("ir-%04d-%02d-%02d", date.year(), date.month(), date.day());
+ setValue(key, value.toString());
+}
+
+MyMoneyAccountLoan::interestDueE MyMoneyAccountLoan::interestCalculation(void) const
+{
+ QString payTime(value("interest-calculation"));
+ if(payTime == "paymentDue")
+ return paymentDue;
+ return paymentReceived;
+}
+
+void MyMoneyAccountLoan::setInterestCalculation(const MyMoneyAccountLoan::interestDueE onReception)
+{
+ if(onReception == paymentDue)
+ setValue("interest-calculation", "paymentDue");
+ else
+ setValue("interest-calculation", "paymentReceived");
+}
+
+const QDate MyMoneyAccountLoan::nextInterestChange(void) const
+{
+ QDate rc;
+
+ QRegExp regExp("(\\d{4})-(\\d{2})-(\\d{2})");
+ if(regExp.search(value("interest-nextchange")) != -1) {
+ rc.setYMD(regExp.cap(1).toInt(), regExp.cap(2).toInt(), regExp.cap(3).toInt());
+ }
+ return rc;
+}
+
+void MyMoneyAccountLoan::setNextInterestChange(const QDate& date)
+{
+ setValue("interest-nextchange", date.toString(Qt::ISODate));
+}
+
+int MyMoneyAccountLoan::interestChangeFrequency(int* unit) const
+{
+ int rc = -1;
+
+ if(unit)
+ *unit = 1;
+
+ QRegExp regExp("(\\d+)/(\\d{1})");
+ if(regExp.search(value("interest-changefrequency")) != -1) {
+ rc = regExp.cap(1).toInt();
+ if(unit != 0) {
+ *unit = regExp.cap(2).toInt();
+ }
+ }
+ return rc;
+}
+
+void MyMoneyAccountLoan::setInterestChangeFrequency(const int amount, const int unit)
+{
+ QString val;
+ val.sprintf("%d/%d", amount, unit);
+ setValue("interest-changeFrequency", val);
+}
+
+const QString MyMoneyAccountLoan::schedule(void) const
+{
+ return QString(value("schedule").latin1());
+}
+
+void MyMoneyAccountLoan::setSchedule(const QString& sched)
+{
+ setValue("schedule", sched);
+}
+
+bool MyMoneyAccountLoan::fixedInterestRate(void) const
+{
+ // make sure, that an empty kvp element returns true
+ return !(value("fixed-interest") == "no");
+}
+
+void MyMoneyAccountLoan::setFixedInterestRate(const bool fixed)
+{
+ setValue("fixed-interest", fixed ? "yes" : "no");
+ if(fixed) {
+ deletePair("interest-nextchange");
+ deletePair("interest-changeFrequency");
+ }
+}
+
+const MyMoneyMoney MyMoneyAccountLoan::finalPayment(void) const
+{
+ return MyMoneyMoney(value("final-payment"));
+}
+
+void MyMoneyAccountLoan::setFinalPayment(const MyMoneyMoney& finalPayment)
+{
+ setValue("final-payment", finalPayment.toString());
+}
+
+unsigned int MyMoneyAccountLoan::term(void) const
+{
+ return value("term").toUInt();
+}
+
+void MyMoneyAccountLoan::setTerm(const unsigned int payments)
+{
+ setValue("term", QString::number(payments));
+}
+
+const MyMoneyMoney MyMoneyAccountLoan::periodicPayment(void) const
+{
+ return MyMoneyMoney(value("periodic-payment"));
+}
+
+void MyMoneyAccountLoan::setPeriodicPayment(const MyMoneyMoney& payment)
+{
+ setValue("periodic-payment", payment.toString());
+}
+
+const QString MyMoneyAccountLoan::payee(void) const
+{
+ return value("payee");
+}
+
+void MyMoneyAccountLoan::setPayee(const QString& payee)
+{
+ setValue("payee", payee);
+}
+
+const QString MyMoneyAccountLoan::interestAccountId(void) const
+{
+ return QString();
+}
+
+void MyMoneyAccountLoan::setInterestAccountId(const QString& /* id */)
+{
+
+}
+
+bool MyMoneyAccountLoan::hasReferenceTo(const QString& id) const
+{
+ return MyMoneyAccount::hasReferenceTo(id)
+ || (id == payee())
+ || (id == schedule());
+}
+
+void MyMoneyAccountLoan::setInterestCompounding(int frequency)
+{
+ setValue("compoundingFrequency", QString("%1").arg(frequency));
+}
+
+int MyMoneyAccountLoan::interestCompounding(void) const
+{
+ return value("compoundingFrequency").toInt();
+}
+
+void MyMoneyAccount::writeXML(QDomDocument& document, QDomElement& parent) const
+{
+ QDomElement el = document.createElement("ACCOUNT");
+
+ writeBaseXML(document, el);
+
+ el.setAttribute("parentaccount", parentAccountId());
+ el.setAttribute("lastreconciled", dateToString(lastReconciliationDate()));
+ el.setAttribute("lastmodified", dateToString(lastModified()));
+ el.setAttribute("institution", institutionId());
+ el.setAttribute("opened", dateToString(openingDate()));
+ el.setAttribute("number", number());
+ // el.setAttribute("openingbalance", openingBalance().toString());
+ el.setAttribute("type", accountType());
+ el.setAttribute("name", name());
+ el.setAttribute("description", description());
+ if(!currencyId().isEmpty())
+ el.setAttribute("currency", currencyId());
+
+ //Add in subaccount information, if this account has subaccounts.
+ if(accountCount())
+ {
+ QDomElement subAccounts = document.createElement("SUBACCOUNTS");
+ QStringList::ConstIterator it;
+ for(it = accountList().begin(); it != accountList().end(); ++it)
+ {
+ QDomElement temp = document.createElement("SUBACCOUNT");
+ temp.setAttribute("id", (*it));
+ subAccounts.appendChild(temp);
+ }
+
+ el.appendChild(subAccounts);
+ }
+
+ // Write online banking settings
+ if(m_onlineBankingSettings.pairs().count()) {
+ QDomElement onlinesettings = document.createElement("ONLINEBANKING");
+ QMap<QString,QString>::const_iterator it_key = m_onlineBankingSettings.pairs().begin();
+ while ( it_key != m_onlineBankingSettings.pairs().end() ) {
+ onlinesettings.setAttribute(it_key.key(), it_key.data());
+ ++it_key;
+ }
+ el.appendChild(onlinesettings);
+ }
+
+ // FIXME drop the lastStatementDate record from the KVP when it is
+ // not stored there after setLastReconciliationDate() has been changed
+ // See comment there when this will happen
+ // deletePair("lastStatementDate");
+
+
+ //Add in Key-Value Pairs for accounts.
+ MyMoneyKeyValueContainer::writeXML(document, el);
+
+ parent.appendChild(el);
+}
+
+bool MyMoneyAccount::hasReferenceTo(const QString& id) const
+{
+ return (id == m_institution) || (id == m_parentAccount) || (id == m_currencyId);
+}
+
+void MyMoneyAccount::setOnlineBankingSettings(const MyMoneyKeyValueContainer& values)
+{
+ m_onlineBankingSettings = values;
+}
+
+const MyMoneyKeyValueContainer& MyMoneyAccount::onlineBankingSettings(void) const
+{
+ return m_onlineBankingSettings;
+}
+
+void MyMoneyAccount::setClosed(bool closed)
+{
+ if(closed)
+ setValue("mm-closed", "yes");
+ else
+ deletePair("mm-closed");
+}
+
+bool MyMoneyAccount::isClosed(void) const
+{
+ return !(value("mm-closed").isEmpty());
+}
+
+int MyMoneyAccount::fraction(const MyMoneySecurity& sec) const
+{
+ int fraction;
+ if(m_accountType == Cash)
+ fraction = sec.smallestCashFraction();
+ else
+ fraction = sec.smallestAccountFraction();
+ return fraction;
+}
+
+int MyMoneyAccount::fraction(const MyMoneySecurity& sec)
+{
+ if(m_accountType == Cash)
+ m_fraction = sec.smallestCashFraction();
+ else
+ m_fraction = sec.smallestAccountFraction();
+ return m_fraction;
+}
+
+int MyMoneyAccount::fraction(void) const
+{
+ Q_ASSERT(m_fraction != -1);
+
+ return m_fraction;
+}
+
+bool MyMoneyAccount::isCategory(void) const
+{
+ return m_accountType == Income || m_accountType == Expense;
+}
+
+QString MyMoneyAccount::brokerageName(void) const
+{
+ if(m_accountType == Investment)
+ return QString("%1 (%2)").arg(m_name, i18n("Brokerage (suffix for account names)", "Brokerage"));
+ return m_name;
+}
+
+void MyMoneyAccount::adjustBalance(const MyMoneySplit& s, bool reverse)
+{
+ if(s.action() == MyMoneySplit::ActionSplitShares) {
+ if(reverse)
+ m_balance = m_balance / s.shares();
+ else
+ m_balance = m_balance * s.shares();
+ } else {
+ if(reverse)
+ m_balance -= s.shares();
+ else
+ m_balance += s.shares();
+ }
+
+}
+
+QPixmap MyMoneyAccount::accountPixmap(bool reconcileFlag, int size) const
+{
+ QString icon;
+ switch(accountType()) {
+ default:
+ if(accountGroup() == MyMoneyAccount::Asset)
+ icon = "account-types_asset";
+ else
+ icon = "account-types_liability";
+ break;
+
+ case MyMoneyAccount::Investment:
+ case MyMoneyAccount::Stock:
+ case MyMoneyAccount::MoneyMarket:
+ case MyMoneyAccount::CertificateDep:
+ icon = "account-types_investments";
+ break;
+
+ case MyMoneyAccount::Checkings:
+ icon = "account-types_checking";
+ break;
+ case MyMoneyAccount::Savings:
+ icon = "account-types_savings";
+ break;
+
+ case MyMoneyAccount::AssetLoan:
+ case MyMoneyAccount::Loan:
+ icon = "account-types_loan";
+ break;
+
+ case MyMoneyAccount::CreditCard:
+ icon = "account-types_credit-card";
+ break;
+
+ case MyMoneyAccount::Asset:
+ icon = "account-types_asset";
+ break;
+
+ case MyMoneyAccount::Cash:
+ icon = "account-types_cash";
+ break;
+
+ case MyMoneyAccount::Income:
+ icon = "account-types_income";
+ break;
+
+ case MyMoneyAccount::Expense:
+ icon = "account-types_expense";
+ break;
+
+ case MyMoneyAccount::Equity:
+ icon = "account";
+ break;
+ }
+
+ QPixmap result = DesktopIcon(icon, size);
+ if(isClosed()) {
+ QPixmap ovly = DesktopIcon("account-types_closed", size);
+ bitBlt(&result, 0, 0, &ovly, 0, 0, ovly.width(), ovly.height(), Qt::CopyROP, false);
+ } else if(reconcileFlag) {
+ QPixmap ovly = DesktopIcon("account-types_reconcile.png", size);
+ bitBlt(&result, 0, 0, &ovly, 0, 0, ovly.width(), ovly.height(), Qt::CopyROP, false);
+ } else if(!onlineBankingSettings().value("provider").isEmpty()) {
+ QPixmap ovly = DesktopIcon("account-types_online.png", size);
+ bitBlt(&result, 0, 0, &ovly, 0, 0, ovly.width(), ovly.height(), Qt::CopyROP, false);
+ }
+ return result;
+}
+
+QString MyMoneyAccount::accountTypeToString(const MyMoneyAccount::accountTypeE accountType)
+{
+ QString returnString;
+
+ switch (accountType) {
+ case MyMoneyAccount::Checkings:
+ returnString = i18n("Checking");
+ break;
+ case MyMoneyAccount::Savings:
+ returnString = i18n("Savings");
+ break;
+ case MyMoneyAccount::CreditCard:
+ returnString = i18n("Credit Card");
+ break;
+ case MyMoneyAccount::Cash:
+ returnString = i18n("Cash");
+ break;
+ case MyMoneyAccount::Loan:
+ returnString = i18n("Loan");
+ break;
+ case MyMoneyAccount::CertificateDep:
+ returnString = i18n("Certificate of Deposit");
+ break;
+ case MyMoneyAccount::Investment:
+ returnString = i18n("Investment");
+ break;
+ case MyMoneyAccount::MoneyMarket:
+ returnString = i18n("Money Market");
+ break;
+ case MyMoneyAccount::Asset:
+ returnString = i18n("Asset");
+ break;
+ case MyMoneyAccount::Liability:
+ returnString = i18n("Liability");
+ break;
+ case MyMoneyAccount::Currency:
+ returnString = i18n("Currency");
+ break;
+ case MyMoneyAccount::Income:
+ returnString = i18n("Income");
+ break;
+ case MyMoneyAccount::Expense:
+ returnString = i18n("Expense");
+ break;
+ case MyMoneyAccount::AssetLoan:
+ returnString = i18n("Investment Loan");
+ break;
+ case MyMoneyAccount::Stock:
+ returnString = i18n("Stock");
+ break;
+ case MyMoneyAccount::Equity:
+ returnString = i18n("Equity");
+ break;
+ default:
+ returnString = i18n("Unknown");
+ }
+
+ return returnString;
+}
diff --git a/kmymoney2/mymoney/mymoneyaccount.h b/kmymoney2/mymoney/mymoneyaccount.h
new file mode 100644
index 0000000..333c500
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyaccount.h
@@ -0,0 +1,692 @@
+/***************************************************************************
+ mymoneyaccount.h
+ -------------------
+ copyright : (C) 2002 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYACCOUNT_H
+#define MYMONEYACCOUNT_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstring.h>
+#include <qdatetime.h>
+#include <qvaluelist.h>
+#include <qstringlist.h>
+#include <qdom.h>
+#include <qpixmap.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include <kmymoney/mymoneymoney.h>
+#include <kmymoney/mymoneyobject.h>
+#include <kmymoney/mymoneykeyvaluecontainer.h>
+#include <kmymoney/mymoneysecurity.h>
+#include <kmymoney/export.h>
+#include "mymoneyutils.h"
+class MyMoneyTransaction;
+class MyMoneyInstitution;
+class MyMoneySplit;
+class MyMoneyObjectContainer;
+
+/**
+ * A representation of an account.
+ * This object represents any type of account, those held at an
+ * institution as well as the accounts used for double entry
+ * accounting.
+ *
+ * Currently, the following account types are known:
+ *
+ * @li UnknownAccountType
+ * @li Checkings
+ * @li Savings
+ * @li Cash
+ * @li CreditCard
+ * @li Loan (collected)
+ * @li CertificateDep
+ * @li Investment
+ * @li MoneyMarket
+ * @li Currency
+ * @li Asset
+ * @li Liability
+ * @li Income
+ * @li Expense
+ * @li Loan (given)
+ * @li Equity
+ *
+ * @see MyMoneyInstitution
+ * @see MyMoneyFile
+ *
+ * @author Michael Edwardes 2000-2001
+ * @author Thomas Baumgart 2002
+ *
+**/
+class KMYMONEY_EXPORT MyMoneyAccount : public MyMoneyObject, public MyMoneyKeyValueContainer
+{
+ friend class MyMoneyObjectContainer;
+public:
+
+ /**
+ * Account types currently supported.
+ */
+ typedef enum _accountTypeE {
+ UnknownAccountType=0, /**< For error handling */
+ Checkings, /**< Standard checking account */
+ Savings, /**< Typical savings account */
+ Cash, /**< Denotes a shoe-box or pillowcase stuffed
+ with cash */
+ CreditCard, /**< Credit card accounts */
+ Loan, /**< Loan and mortgage accounts (liability) */
+ CertificateDep, /**< Certificates of Deposit */
+ Investment, /**< Investment account */
+ MoneyMarket, /**< Money Market Account */
+ Asset, /**< Denotes a generic asset account.*/
+ Liability, /**< Denotes a generic liability account.*/
+ Currency, /**< Denotes a currency trading account. */
+ Income, /**< Denotes an income account */
+ Expense, /**< Denotes an expense account */
+ AssetLoan, /**< Denotes a loan (asset of the owner of this object) */
+ Stock, /**< Denotes an security account as sub-account for an investment */
+ Equity, /**< Denotes an equity account e.g. opening/closeing balance */
+
+ /* insert new account types above this line */
+ MaxAccountTypes /**< Denotes the number of different account types */
+ }accountTypeE;
+
+ /**
+ * This is the constructor for a new empty account
+ */
+ MyMoneyAccount();
+
+ /**
+ * This is the constructor for a new account known to the current file
+ * This is the only constructor that will set the attribute m_openingDate
+ * to a correct value.
+ *
+ * @param id id assigned to the account
+ * @param right account definition
+ */
+ MyMoneyAccount(const QString& id, const MyMoneyAccount& right);
+
+ /**
+ * This is the constructor for an account that is described by a
+ * QDomElement (e.g. from a file).
+ *
+ * @param el const reference to the QDomElement from which to
+ * create the object
+ */
+ MyMoneyAccount(const QDomElement& el);
+
+ /**
+ * This is the destructor for any MyMoneyAccount object
+ */
+ ~MyMoneyAccount();
+
+ /**
+ * This operator tests for equality of two MyMoneyAccount objects
+ */
+ bool operator == (const MyMoneyAccount &) const;
+
+ /**
+ * This converts the account type into one of the four
+ * major account types liability, asset, expense or income.
+ *
+ * The current assignment is as follows:
+ *
+ * - Asset
+ * - Asset
+ * - Checkings
+ * - Savings
+ * - Cash
+ * - Currency
+ * - Investment
+ * - MoneyMarket
+ * - CertificateDep
+ * - AssetLoan
+ * - Stock
+ *
+ * - Liability
+ * - Liability
+ * - CreditCard
+ * - Loan
+ *
+ * - Income
+ * - Income
+ *
+ * - Expense
+ * - Expense
+ *
+ * @param type actual account type
+ * @return accountTypeE of major account type
+ */
+ static MyMoneyAccount::accountTypeE accountGroup(MyMoneyAccount::accountTypeE type);
+
+ MyMoneyAccount::accountTypeE accountGroup(void) const;
+
+ /**
+ * This method returns the id of the MyMoneyInstitution object this account
+ * belongs to.
+ * @return id of MyMoneyInstitution object. QString() if it is
+ * an internal account
+ * @see setInstitution
+ */
+ const QString& institutionId(void) const { return m_institution; }
+
+ /**
+ * This method returns the name of the account
+ * @return name of account
+ * @see setName()
+ */
+ const QString& name(void) const { return m_name; }
+
+ /**
+ * This method returns the number of the account at the institution
+ * @return number of account at the institution
+ * @see setNumber
+ */
+ const QString& number(void) const { return m_number; }
+
+ /**
+ * This method returns the descriptive text of the account.
+ * @return description of account
+ * @see setDescription
+ */
+ const QString& description(void) const { return m_description; }
+
+ /**
+ * This method returns the opening date of this account
+ * @return date of opening of this account as const QDate value
+ * @see setOpeningDate()
+ */
+ const QDate& openingDate(void) const { return m_openingDate; }
+
+ /**
+ * This method returns the date of the last reconciliation of this account
+ * @return date of last reconciliation as const QDate value
+ * @see setLastReconciliationDate
+ */
+ const QDate& lastReconciliationDate(void) const { return m_lastReconciliationDate; }
+
+ /**
+ * This method returns the date the account was last modified
+ * @return date of last modification as const QDate value
+ * @see setLastModified
+ */
+ const QDate& lastModified(void) const { return m_lastModified; }
+
+ /**
+ * This method is used to return the ID of the parent account
+ * @return QString with the ID of the parent of this account
+ */
+ const QString& parentAccountId(void) const { return m_parentAccount; };
+
+ /**
+ * This method returns the list of the account id's of
+ * subordinate accounts
+ * @return QStringList account ids
+ */
+ const QStringList& accountList(void) const { return m_accountList; };
+
+ /**
+ * This method returns the number of entries in the m_accountList
+ * @return number of entries in the accountList
+ */
+ int accountCount(void) const { return m_accountList.count(); };
+
+ /**
+ * This method is used to add an account id as sub-ordinate account
+ * @param account const QString reference to account ID
+ */
+ void addAccountId(const QString& account);
+
+ /**
+ * This method is used to remove an account from the list of
+ * sub-ordinate accounts.
+ * @param account const QString reference to account ID to be removed
+ */
+ void removeAccountId(const QString& account);
+
+ /**
+ * This method is used to remove all accounts from the list of
+ * sub-ordinate accounts.
+ */
+ void removeAccountIds(void);
+
+ /**
+ * This method is used to modify the date of the last
+ * modification access.
+ * @param date date of last modification
+ * @see lastModified
+ */
+ void setLastModified(const QDate& date);
+
+ /**
+ * This method is used to set the name of the account
+ * @param name name of the account
+ * @see name
+ */
+ void setName(const QString& name);
+
+ /**
+ * This method is used to set the number of the account at the institution
+ * @param number number of the account
+ * @see number
+ */
+ void setNumber(const QString& number);
+
+ /**
+ * This method is used to set the descriptive text of the account
+ *
+ * @param desc text that serves as description
+ * @see setDescription
+ */
+ void setDescription(const QString& desc);
+
+ /**
+ * This method is used to set the id of the institution this account
+ * belongs to.
+ *
+ * @param id id of the institution this account belongs to
+ * @see institution
+ */
+ void setInstitutionId(const QString& id);
+
+ /**
+ * This method is used to set the opening date information of an
+ * account.
+ *
+ * @param date QDate of opening date
+ * @see openingDate
+ */
+ void setOpeningDate(const QDate& date);
+
+ /**
+ * This method is used to set the date of the last reconciliation
+ * of an account.
+ * @param date QDate of last reconciliation
+ * @see lastReconciliationDate
+ */
+ void setLastReconciliationDate(const QDate& date);
+
+ /**
+ * This method is used to change the account type
+ *
+ * @param type account type
+ */
+ void setAccountType(const accountTypeE type);
+
+ /**
+ * This method is used to set a new parent account id
+ * @param parent QString reference to new parent account
+ */
+ void setParentAccountId(const QString& parent);
+
+ /**
+ * This method is used to update m_lastModified to the current date
+ */
+ void touch(void) { setLastModified(QDate::currentDate()); }
+
+ /**
+ * This method returns the type of the account.
+ */
+ accountTypeE accountType(void) const { return m_accountType; }
+
+ /**
+ * This method retrieves the id of the currency used with this account.
+ * If the return value is empty, the base currency should be used.
+ *
+ * @return id of currency
+ */
+ const QString& currencyId(void) const { return m_currencyId; };
+
+ /**
+ * This method sets the id of the currency used with this account.
+ *
+ * @param id ID of currency to be associated with this account.
+ */
+ void setCurrencyId(const QString& id);
+
+ void writeXML(QDomDocument& document, QDomElement& parent) const;
+
+ /**
+ * This method checks if a reference to the given object exists. It returns,
+ * a @p true if the object is referencing the one requested by the
+ * parameter @p id. If it does not, this method returns @p false.
+ *
+ * @param id id of the object to be checked for references
+ * @retval true This object references object with id @p id.
+ * @retval false This object does not reference the object with id @p id.
+ */
+ virtual bool hasReferenceTo(const QString& id) const;
+
+ /**
+ * This member returns the balance of this account based on
+ * all transactions stored in the journal.
+ */
+ const MyMoneyMoney& balance(void) const { return m_balance; }
+
+ /**
+ * This method adjusts the balance of this account
+ * according to the difference contained in the split @p s.
+ * If the s.action() is MyMoneySplit::ActionSplitShares then
+ * the balance will be adjusted accordingly.
+ *
+ * @param s const reference to MyMoneySplit object containing the
+ * value to be added/subtracted to/from the balance
+ * @param reverse add (false) or subtract (true) the shares contained in the split.
+ * It also affects the balance for share splits in the opposite direction.
+ */
+ void adjustBalance(const MyMoneySplit& s, bool reverse = false);
+
+ /**
+ * This method sets the balance of this account
+ * according to the value provided by @p val.
+ *
+ * @param val const reference to MyMoneyMoney object containing the
+ * value to be assigned to the balance
+ */
+ void setBalance(const MyMoneyMoney& val) { m_balance = val; }
+
+ /**
+ * This method sets the kvp's for online banking with this account
+ *
+ * @param values The container of kvp's needed when connecting to this account
+ */
+ void setOnlineBankingSettings(const MyMoneyKeyValueContainer& values);
+
+ /**
+ * This method retrieves the kvp's for online banking with this account
+ *
+ * @return The container of kvp's needed when connecting to this account
+ */
+ const MyMoneyKeyValueContainer& onlineBankingSettings(void) const;
+
+ /**
+ * This method sets the closed flag for the account. This is just
+ * an informational flag for the application. It has no other influence
+ * on the behaviour of the account object. The default for
+ * new objects @p open.
+ *
+ * @param isClosed mark the account closed (@p true) or open (@p false).
+ */
+ void setClosed(bool isClosed);
+
+ /**
+ * Return the closed flag for the account.
+ *
+ * @retval false account is marked open (the default for new accounts)
+ * @retval true account is marked closed
+ */
+ bool isClosed(void) const;
+
+ /**
+ * returns the applicable smallest fraction for this account
+ * for the given security based on the account type. At the same
+ * time, m_fraction is updated to the value returned.
+ *
+ * @param sec const reference to currency (security)
+ *
+ * @retval sec.smallestCashFraction() for account type Cash
+ * @retval sec.smallestAccountFraction() for all other account types
+ */
+ int fraction(const MyMoneySecurity& sec);
+
+ /**
+ * Same as the above method, but does not modify m_fraction.
+ */
+ int fraction(const MyMoneySecurity& sec) const;
+
+ /**
+ * This method returns the stored value for the fraction of this
+ * account or -1 if not initialized. It can be initialized by
+ * calling fraction(const MyMoneySecurity& sec) once.
+ *
+ * @note Don't use this method outside of KMyMoney application context (eg. testcases).
+ * Use the above method instead.
+ */
+ int fraction(void) const;
+
+ /**
+ * This method returns @a true if the account type is
+ * either Income or Expense
+ *
+ * @retval true account is of type income or expense
+ * @retval false for all other account types
+ *
+ * @deprecated use isIncomeExpense() instead
+ */
+ bool isCategory(void) const __attribute__ ((deprecated));
+
+ /**
+ * This method returns @a true if the account type is
+ * either Income or Expense
+ *
+ * @retval true account is of type income or expense
+ * @retval false for all other account types
+ */
+ bool isIncomeExpense(void) const;
+
+ /**
+ * This method returns @a true if the account type is
+ * either Asset or Liability
+ *
+ * @retval true account is of type asset or liability
+ * @retval false for all other account types
+ */
+ bool isAssetLiability(void) const;
+
+ /**
+ * This method returns @a true if the account type is
+ * either AssetLoan or Loan
+ *
+ * @retval true account is of type Loan or AssetLoan
+ * @retval false for all other account types
+ */
+ bool isLoan(void) const;
+
+ /**
+ * This method returns @a true if the account type is
+ * Stock
+ *
+ * @retval true account is of type Stock
+ * @retval false for all other account types
+ */
+ bool isInvest(void) const;
+
+ /**
+ * This method returns a name that has a brokerage suffix of
+ * the current name. It only works on investment accounts and
+ * returns the name for all other cases.
+ */
+ QString brokerageName(void) const;
+
+ /**
+ * @param size is a hint for the size of the icon
+ * @return a pixmap using DesktopIcon for the account type
+ */
+ QPixmap accountPixmap(bool reconcileFlag = false, int size = 0) const;
+
+ /**
+ * This method is used to convert the internal representation of
+ * an account type into a human readable format
+ *
+ * @param accountType numerical representation of the account type.
+ * For possible values, see MyMoneyAccount::accountTypeE
+ * @return QString representing the human readable form
+ */
+ static QString accountTypeToString(const MyMoneyAccount::accountTypeE accountType);
+
+ KMYMONEY_EXPORT QDataStream &operator<<( const MyMoneyAccount & );
+ KMYMONEY_EXPORT QDataStream &operator>>( MyMoneyAccount & );
+
+private:
+ /**
+ * This member variable identifies the type of account
+ */
+ accountTypeE m_accountType;
+
+ /**
+ * This member variable keeps the ID of the MyMoneyInstitution object
+ * that this object belongs to.
+ */
+ QString m_institution;
+
+ /**
+ * This member variable keeps the name of the account
+ * It is solely for documentation purposes and it's contents is not
+ * used otherwise by the mymoney-engine.
+ */
+ QString m_name;
+
+ /**
+ * This member variable keeps the account number at the institution
+ * It is solely for documentation purposes and it's contents is not
+ * used otherwise by the mymoney-engine.
+ */
+ QString m_number;
+
+ /**
+ * This member variable is a description of the account.
+ * It is solely for documentation purposes and it's contents is not
+ * used otherwise by the mymoney-engine.
+ */
+ QString m_description;
+
+ /**
+ * This member variable keeps the date when the account
+ * was last modified.
+ */
+ QDate m_lastModified;
+
+ /**
+ * This member variable keeps the date when the
+ * account was created as an object in a MyMoneyFile
+ */
+ QDate m_openingDate;
+
+ /**
+ * This member variable keeps the date of the last
+ * reconciliation of this account
+ */
+ QDate m_lastReconciliationDate;
+
+ /**
+ * This member holds the ID's of all sub-ordinate accounts
+ */
+ QStringList m_accountList;
+
+ /**
+ * This member contains the ID of the parent account
+ */
+ QString m_parentAccount;
+
+ /**
+ * This member contains the ID of the currency associated with this account
+ */
+ QString m_currencyId;
+
+ /**
+ * This member holds the balance of all transactions stored in the journal
+ * for this account.
+ */
+ MyMoneyMoney m_balance;
+
+ /**
+ * This member variable keeps the set of kvp's needed to establish
+ * online banking sessions to this account.
+ */
+ MyMoneyKeyValueContainer m_onlineBankingSettings;
+
+ /**
+ * This member keeps the fraction for the account. It is filled by MyMoneyFile
+ * when set to -1. See also @sa fraction(const MyMoneySecurity&).
+ */
+ int m_fraction;
+
+};
+
+/**
+ * This class is a convenience class to access data for loan accounts.
+ * It does contain the same member variables as a MyMoneyAccount object,
+ * but serves a set of getter/setter methods to ease the access to
+ * laon relevant data stored in the key value container of the MyMoneyAccount
+ * object.
+ */
+class KMYMONEY_EXPORT MyMoneyAccountLoan : public MyMoneyAccount
+{
+public:
+ enum interestDueE {
+ paymentDue = 0,
+ paymentReceived
+ };
+
+ enum interestChangeUnitE {
+ changeDaily = 0,
+ changeWeekly,
+ changeMonthly,
+ changeYearly
+ };
+
+ MyMoneyAccountLoan() {}
+ MyMoneyAccountLoan(const MyMoneyAccount&);
+ ~MyMoneyAccountLoan() {}
+
+ const MyMoneyMoney loanAmount(void) const;
+ void setLoanAmount(const MyMoneyMoney& amount);
+ const MyMoneyMoney interestRate(const QDate& date) const;
+ void setInterestRate(const QDate& date, const MyMoneyMoney& rate);
+ interestDueE interestCalculation(void) const;
+ void setInterestCalculation(const interestDueE onReception);
+ const QDate nextInterestChange(void) const;
+ void setNextInterestChange(const QDate& date);
+ const QString schedule(void) const;
+ void setSchedule(const QString& sched);
+ bool fixedInterestRate(void) const;
+ void setFixedInterestRate(const bool fixed);
+ const MyMoneyMoney finalPayment(void) const;
+ void setFinalPayment(const MyMoneyMoney& finalPayment);
+ unsigned int term(void) const;
+ void setTerm(const unsigned int payments);
+ int interestChangeFrequency(int* unit = 0) const;
+ void setInterestChangeFrequency(const int amount, const int unit);
+ const MyMoneyMoney periodicPayment(void) const;
+ void setPeriodicPayment(const MyMoneyMoney& payment);
+ int interestCompounding(void) const;
+ void setInterestCompounding(int frequency);
+ const QString payee(void) const;
+ void setPayee(const QString& payee);
+ const QString interestAccountId(void) const;
+ void setInterestAccountId(const QString& id);
+
+ /**
+ * This method checks if a reference to the given object exists. It returns,
+ * a @p true if the object is referencing the one requested by the
+ * parameter @p id. If it does not, this method returns @p false.
+ *
+ * @param id id of the object to be checked for references
+ * @retval true This object references object with id @p id.
+ * @retval false This object does not reference the object with id @p id.
+ */
+ virtual bool hasReferenceTo(const QString& id) const;
+
+};
+
+#endif
+
+
diff --git a/kmymoney2/mymoney/mymoneyaccounttest.cpp b/kmymoney2/mymoney/mymoneyaccounttest.cpp
new file mode 100644
index 0000000..1384c5c
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyaccounttest.cpp
@@ -0,0 +1,589 @@
+/***************************************************************************
+ mymoneyaccounttest.cpp
+ -------------------
+ copyright : (C) 2002 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "mymoneyaccounttest.h"
+#include <kmymoney/mymoneyexception.h>
+#include <kmymoney/mymoneysplit.h>
+
+MyMoneyAccountTest::MyMoneyAccountTest()
+{
+}
+
+
+void MyMoneyAccountTest::setUp () {
+}
+
+void MyMoneyAccountTest::tearDown () {
+}
+
+void MyMoneyAccountTest::testEmptyConstructor() {
+ MyMoneyAccount a;
+
+ CPPUNIT_ASSERT(a.id().isEmpty());
+ CPPUNIT_ASSERT(a.name().isEmpty());
+ CPPUNIT_ASSERT(a.accountType() == MyMoneyAccount::UnknownAccountType);
+ CPPUNIT_ASSERT(a.openingDate() == QDate());
+ CPPUNIT_ASSERT(a.lastModified() == QDate());
+ CPPUNIT_ASSERT(a.lastReconciliationDate() == QDate());
+ CPPUNIT_ASSERT(a.accountList().count() == 0);
+ CPPUNIT_ASSERT(a.balance().isZero());
+}
+
+void MyMoneyAccountTest::testConstructor() {
+ QString id = "A000001";
+ QString institutionid = "B000001";
+ QString parent = "Parent";
+ MyMoneyAccount r;
+ MyMoneySplit s;
+ r.setAccountType(MyMoneyAccount::Asset);
+ r.setOpeningDate(QDate::currentDate());
+ r.setLastModified(QDate::currentDate());
+ r.setDescription("Desc");
+ r.setNumber("465500");
+ r.setParentAccountId(parent);
+ r.setValue(QString("key"), "value");
+ s.setShares(MyMoneyMoney(1,1));
+ r.adjustBalance(s);
+ CPPUNIT_ASSERT(r.m_kvp.count() == 1);
+ CPPUNIT_ASSERT(r.value("key") == "value");
+
+ MyMoneyAccount a(id, r);
+
+ CPPUNIT_ASSERT(a.id() == id);
+ CPPUNIT_ASSERT(a.institutionId().isEmpty());
+ CPPUNIT_ASSERT(a.accountType() == MyMoneyAccount::Asset);
+ CPPUNIT_ASSERT(a.openingDate() == QDate::currentDate());
+ CPPUNIT_ASSERT(a.lastModified() == QDate::currentDate());
+ CPPUNIT_ASSERT(a.number() == "465500");
+ CPPUNIT_ASSERT(a.description() == "Desc");
+ CPPUNIT_ASSERT(a.accountList().count() == 0);
+ CPPUNIT_ASSERT(a.parentAccountId() == "Parent");
+ CPPUNIT_ASSERT(a.balance() == MyMoneyMoney(1,1));
+
+ QMap<QString, QString> copy;
+ copy = r.pairs();
+ CPPUNIT_ASSERT(copy.count() == 1);
+ CPPUNIT_ASSERT(copy[QString("key")] == "value");
+}
+
+void MyMoneyAccountTest::testSetFunctions() {
+ MyMoneyAccount a;
+
+ QDate today(QDate::currentDate());
+ CPPUNIT_ASSERT(a.name().isEmpty());
+ CPPUNIT_ASSERT(a.lastModified() == QDate());
+ CPPUNIT_ASSERT(a.description().isEmpty());
+
+ a.setName("Account");
+ a.setInstitutionId("Institution1");
+ a.setLastModified(today);
+ a.setDescription("Desc");
+ a.setNumber("123456");
+ a.setAccountType(MyMoneyAccount::MoneyMarket);
+
+ CPPUNIT_ASSERT(a.name() == "Account");
+ CPPUNIT_ASSERT(a.institutionId() == "Institution1");
+ CPPUNIT_ASSERT(a.lastModified() == today);
+ CPPUNIT_ASSERT(a.description() == "Desc");
+ CPPUNIT_ASSERT(a.number() == "123456");
+ CPPUNIT_ASSERT(a.accountType() == MyMoneyAccount::MoneyMarket);
+}
+
+void MyMoneyAccountTest::testCopyConstructor() {
+ QString id = "A000001";
+ QString institutionid = "B000001";
+ QString parent = "ParentAccount";
+ MyMoneyAccount r;
+ r.setAccountType(MyMoneyAccount::Expense);
+ r.setOpeningDate(QDate::currentDate());
+ r.setLastModified(QDate::currentDate());
+ r.setName("Account");
+ r.setInstitutionId("Inst1");
+ r.setDescription("Desc1");
+ r.setNumber("Number");
+ r.setParentAccountId(parent);
+ r.setValue("Key", "Value");
+
+ MyMoneyAccount a(id, r);
+ a.setInstitutionId(institutionid);
+
+ MyMoneyAccount b(a);
+
+ CPPUNIT_ASSERT(b.name() == "Account");
+ CPPUNIT_ASSERT(b.institutionId() == institutionid);
+ CPPUNIT_ASSERT(b.accountType() == MyMoneyAccount::Expense);
+ CPPUNIT_ASSERT(b.lastModified() == QDate::currentDate());
+ CPPUNIT_ASSERT(b.openingDate() == QDate::currentDate());
+ CPPUNIT_ASSERT(b.description() == "Desc1");
+ CPPUNIT_ASSERT(b.number() == "Number");
+ CPPUNIT_ASSERT(b.parentAccountId() == "ParentAccount");
+
+ CPPUNIT_ASSERT(b.value("Key") == "Value");
+}
+
+void MyMoneyAccountTest::testAssignmentConstructor() {
+ MyMoneyAccount a;
+ a.setAccountType(MyMoneyAccount::Checkings);
+ a.setName("Account");
+ a.setInstitutionId("Inst1");
+ a.setDescription("Bla");
+ a.setNumber("assigned Number");
+ a.setValue("Key", "Value");
+ a.addAccountId("ChildAccount");
+
+ MyMoneyAccount b;
+
+ b.setLastModified(QDate::currentDate());
+
+ b = a;
+
+ CPPUNIT_ASSERT(b.name() == "Account");
+ CPPUNIT_ASSERT(b.institutionId() == "Inst1");
+ CPPUNIT_ASSERT(b.accountType() == MyMoneyAccount::Checkings);
+ CPPUNIT_ASSERT(b.lastModified() == QDate());
+ CPPUNIT_ASSERT(b.openingDate() == a.openingDate());
+ CPPUNIT_ASSERT(b.description() == "Bla");
+ CPPUNIT_ASSERT(b.number() == "assigned Number");
+ CPPUNIT_ASSERT(b.value("Key") == "Value");
+ CPPUNIT_ASSERT(b.accountList().count() == 1);
+ CPPUNIT_ASSERT(b.accountList()[0] == "ChildAccount");
+}
+
+void MyMoneyAccountTest::testAdjustBalance() {
+ MyMoneyAccount a;
+ MyMoneySplit s;
+ s.setShares(MyMoneyMoney(3,1));
+ a.adjustBalance(s);
+ CPPUNIT_ASSERT(a.balance() == MyMoneyMoney(3,1));
+ s.setShares(MyMoneyMoney(5,1));
+ a.adjustBalance(s, true);
+ CPPUNIT_ASSERT(a.balance() == MyMoneyMoney(-2,1));
+ s.setShares(MyMoneyMoney(2,1));
+ s.setAction(MyMoneySplit::ActionSplitShares);
+ a.adjustBalance(s);
+ CPPUNIT_ASSERT(a.balance() == MyMoneyMoney(-4,1));
+ s.setShares(MyMoneyMoney(4,1));
+ s.setAction(QString());
+ a.adjustBalance(s);
+ CPPUNIT_ASSERT(a.balance().isZero());
+}
+
+void MyMoneyAccountTest::testSubAccounts()
+{
+ MyMoneyAccount a;
+ a.setAccountType(MyMoneyAccount::Checkings);
+
+ a.addAccountId("Subaccount1");
+ CPPUNIT_ASSERT(a.accountList().count() == 1);
+ a.addAccountId("Subaccount1");
+ CPPUNIT_ASSERT(a.accountList().count() == 1);
+ a.addAccountId("Subaccount2");
+ CPPUNIT_ASSERT(a.accountList().count() == 2);
+
+}
+
+void MyMoneyAccountTest::testEquality()
+{
+ MyMoneyAccount a;
+
+ a.setLastModified(QDate::currentDate());
+ a.setName("Name");
+ a.setNumber("Number");
+ a.setDescription("Desc");
+ a.setInstitutionId("I-ID");
+ a.setOpeningDate(QDate::currentDate());
+ a.setLastReconciliationDate(QDate::currentDate());
+ a.setAccountType(MyMoneyAccount::Asset);
+ a.setParentAccountId("P-ID");
+ a.setId("A-ID");
+ a.setCurrencyId("C-ID");
+ a.setValue("Key", "Value");
+
+ MyMoneyAccount b;
+
+ b = a;
+ CPPUNIT_ASSERT(b == a);
+
+ a.setName("Noname");
+ CPPUNIT_ASSERT(!(b == a));
+ b = a;
+
+ a.setLastModified(QDate::currentDate().addDays(-1));
+ CPPUNIT_ASSERT(!(b == a));
+ b = a;
+
+ a.setNumber("Nonumber");
+ CPPUNIT_ASSERT(!(b == a));
+ b = a;
+
+ a.setDescription("NoDesc");
+ CPPUNIT_ASSERT(!(b == a));
+ b = a;
+
+ a.setInstitutionId("I-noID");
+ CPPUNIT_ASSERT(!(b == a));
+ b = a;
+
+ a.setOpeningDate(QDate::currentDate().addDays(-1));
+ CPPUNIT_ASSERT(!(b == a));
+ b = a;
+
+ a.setLastReconciliationDate(QDate::currentDate().addDays(-1));
+ CPPUNIT_ASSERT(!(b == a));
+ b = a;
+
+ a.setAccountType(MyMoneyAccount::Liability);
+ CPPUNIT_ASSERT(!(b == a));
+ b = a;
+
+ a.setParentAccountId("P-noID");
+ CPPUNIT_ASSERT(!(b == a));
+ b = a;
+
+ a.setId("A-noID");
+ CPPUNIT_ASSERT(!(b == a));
+ b = a;
+
+ a.setCurrencyId("C-noID");
+ CPPUNIT_ASSERT(!(b == a));
+ b = a;
+
+ a.setValue("Key", "noValue");
+ CPPUNIT_ASSERT(!(b == a));
+ b = a;
+
+ a.setValue("noKey", "Value");
+ CPPUNIT_ASSERT(!(b == a));
+ b = a;
+
+}
+
+void MyMoneyAccountTest::testWriteXML() {
+ QString id = "A000001";
+ QString institutionid = "B000001";
+ QString parent = "Parent";
+
+ MyMoneyAccount r;
+ r.setAccountType(MyMoneyAccount::Asset);
+ r.setOpeningDate(QDate::currentDate());
+ r.setLastModified(QDate::currentDate());
+ r.setDescription("Desc");
+ r.setName("AccountName");
+ r.setNumber("465500");
+ r.setParentAccountId(parent);
+ r.setInstitutionId(institutionid);
+ r.setValue(QString("key"), "value");
+ r.addAccountId("A000002");
+ // CPPUNIT_ASSERT(r.m_kvp.count() == 1);
+ // CPPUNIT_ASSERT(r.value("key") == "value");
+
+ MyMoneyAccount a(id, r);
+
+ QDomDocument doc("TEST");
+ QDomElement el = doc.createElement("ACCOUNT-CONTAINER");
+ doc.appendChild(el);
+ a.writeXML(doc, el);
+
+ QString ref = QString(
+ "<!DOCTYPE TEST>\n"
+ "<ACCOUNT-CONTAINER>\n"
+ " <ACCOUNT parentaccount=\"Parent\" lastmodified=\"%1\" lastreconciled=\"\" institution=\"B000001\" number=\"465500\" opened=\"%2\" type=\"9\" id=\"A000001\" name=\"AccountName\" description=\"Desc\" >\n"
+ " <SUBACCOUNTS>\n"
+ " <SUBACCOUNT id=\"A000002\" />\n"
+ " </SUBACCOUNTS>\n"
+ " <KEYVALUEPAIRS>\n"
+ " <PAIR key=\"key\" value=\"value\" />\n"
+ " </KEYVALUEPAIRS>\n"
+ " </ACCOUNT>\n"
+ "</ACCOUNT-CONTAINER>\n").
+ arg(QDate::currentDate().toString(Qt::ISODate)).arg(QDate::currentDate().toString(Qt::ISODate));
+
+ CPPUNIT_ASSERT(doc.toString() == ref);
+}
+
+void MyMoneyAccountTest::testReadXML() {
+ MyMoneyAccount a;
+ QString ref_ok = QString(
+ "<!DOCTYPE TEST>\n"
+ "<ACCOUNT-CONTAINER>\n"
+ " <ACCOUNT parentaccount=\"Parent\" lastmodified=\"%1\" lastreconciled=\"\" institution=\"B000001\" number=\"465500\" opened=\"%2\" type=\"9\" id=\"A000001\" name=\"AccountName\" description=\"Desc\" >\n"
+ " <SUBACCOUNTS>\n"
+ " <SUBACCOUNT id=\"A000002\" />\n"
+ " <SUBACCOUNT id=\"A000003\" />\n"
+ " </SUBACCOUNTS>\n"
+ " <KEYVALUEPAIRS>\n"
+ " <PAIR key=\"key\" value=\"value\" />\n"
+ " <PAIR key=\"Key\" value=\"Value\" />\n"
+ " </KEYVALUEPAIRS>\n"
+ " </ACCOUNT>\n"
+ "</ACCOUNT-CONTAINER>\n").
+ arg(QDate::currentDate().toString(Qt::ISODate)).arg(QDate::currentDate().toString(Qt::ISODate));
+
+ QString ref_false = QString(
+ "<!DOCTYPE TEST>\n"
+ "<ACCOUNT-CONTAINER>\n"
+ " <KACCOUNT parentaccount=\"Parent\" lastmodified=\"%1\" lastreconciled=\"\" institution=\"B000001\" number=\"465500\" opened=\"%2\" type=\"9\" id=\"A000001\" name=\"AccountName\" description=\"Desc\" >\n"
+ " <SUBACCOUNTS>\n"
+ " <SUBACCOUNT id=\"A000002\" />\n"
+ " <SUBACCOUNT id=\"A000003\" />\n"
+ " </SUBACCOUNTS>\n"
+ " <KEYVALUEPAIRS>\n"
+ " <PAIR key=\"key\" value=\"value\" />\n"
+ " <PAIR key=\"Key\" value=\"Value\" />\n"
+ " </KEYVALUEPAIRS>\n"
+ " </KACCOUNT>\n"
+ "</ACCOUNT-CONTAINER>\n").
+ arg(QDate::currentDate().toString(Qt::ISODate)).arg(QDate::currentDate().toString(Qt::ISODate));
+
+ QDomDocument doc;
+ QDomElement node;
+ doc.setContent(ref_false);
+ node = doc.documentElement().firstChild().toElement();
+
+ try {
+ a = MyMoneyAccount(node);
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+
+ doc.setContent(ref_ok);
+ node = doc.documentElement().firstChild().toElement();
+
+ a.addAccountId("TEST");
+ a.setValue("KEY", "VALUE");
+
+ try {
+ a = MyMoneyAccount(node);
+ CPPUNIT_ASSERT(a.id() == "A000001");
+ CPPUNIT_ASSERT(a.m_name == "AccountName");
+ CPPUNIT_ASSERT(a.m_parentAccount == "Parent");
+ CPPUNIT_ASSERT(a.m_lastModified == QDate::currentDate());
+ CPPUNIT_ASSERT(a.m_lastReconciliationDate == QDate());
+ CPPUNIT_ASSERT(a.m_institution == "B000001");
+ CPPUNIT_ASSERT(a.m_number == "465500");
+ CPPUNIT_ASSERT(a.m_openingDate == QDate::currentDate());
+ CPPUNIT_ASSERT(a.m_accountType == MyMoneyAccount::Asset);
+ CPPUNIT_ASSERT(a.m_description == "Desc");
+ CPPUNIT_ASSERT(a.accountList().count() == 2);
+ CPPUNIT_ASSERT(a.accountList()[0] == "A000002");
+ CPPUNIT_ASSERT(a.accountList()[1] == "A000003");
+ CPPUNIT_ASSERT(a.pairs().count() == 3);
+ CPPUNIT_ASSERT(a.value("key") == "value");
+ CPPUNIT_ASSERT(a.value("Key") == "Value");
+ CPPUNIT_ASSERT(a.value("lastStatementDate").isEmpty());
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneyAccountTest::testHasReferenceTo(void)
+{
+ MyMoneyAccount a;
+
+ a.setInstitutionId("I0001");
+ a.addAccountId("A_001");
+ a.addAccountId("A_002");
+ a.setParentAccountId("A_Parent");
+ a.setCurrencyId("Currency");
+
+ CPPUNIT_ASSERT(a.hasReferenceTo("I0001") == true);
+ CPPUNIT_ASSERT(a.hasReferenceTo("I0002") == false);
+ CPPUNIT_ASSERT(a.hasReferenceTo("A_001") == false);
+ CPPUNIT_ASSERT(a.hasReferenceTo("A_Parent") == true);
+ CPPUNIT_ASSERT(a.hasReferenceTo("Currency") == true);
+}
+
+void MyMoneyAccountTest::testSetClosed(void)
+{
+ MyMoneyAccount a;
+
+ CPPUNIT_ASSERT(a.isClosed() == false);
+ a.setClosed(true);
+ CPPUNIT_ASSERT(a.isClosed() == true);
+ a.setClosed(false);
+ CPPUNIT_ASSERT(a.isClosed() == false);
+}
+
+void MyMoneyAccountTest::testIsIncomeExpense(void)
+{
+ MyMoneyAccount a;
+
+ a.setAccountType(MyMoneyAccount::UnknownAccountType);
+ CPPUNIT_ASSERT(a.isIncomeExpense() == false);
+
+ a.setAccountType(MyMoneyAccount::Checkings);
+ CPPUNIT_ASSERT(a.isIncomeExpense() == false);
+
+ a.setAccountType(MyMoneyAccount::Savings);
+ CPPUNIT_ASSERT(a.isIncomeExpense() == false);
+
+ a.setAccountType(MyMoneyAccount::Cash);
+ CPPUNIT_ASSERT(a.isIncomeExpense() == false);
+
+ a.setAccountType(MyMoneyAccount::CreditCard);
+ CPPUNIT_ASSERT(a.isIncomeExpense() == false);
+
+ a.setAccountType(MyMoneyAccount::Loan);
+ CPPUNIT_ASSERT(a.isIncomeExpense() == false);
+
+ a.setAccountType(MyMoneyAccount::CertificateDep);
+ CPPUNIT_ASSERT(a.isIncomeExpense() == false);
+
+ a.setAccountType(MyMoneyAccount::Investment);
+ CPPUNIT_ASSERT(a.isIncomeExpense() == false);
+
+ a.setAccountType(MyMoneyAccount::MoneyMarket);
+ CPPUNIT_ASSERT(a.isIncomeExpense() == false);
+
+ a.setAccountType(MyMoneyAccount::Asset);
+ CPPUNIT_ASSERT(a.isIncomeExpense() == false);
+
+ a.setAccountType(MyMoneyAccount::Liability);
+ CPPUNIT_ASSERT(a.isIncomeExpense() == false);
+
+ a.setAccountType(MyMoneyAccount::Currency);
+ CPPUNIT_ASSERT(a.isIncomeExpense() == false);
+
+ a.setAccountType(MyMoneyAccount::Income);
+ CPPUNIT_ASSERT(a.isIncomeExpense() == true);
+
+ a.setAccountType(MyMoneyAccount::Expense);
+ CPPUNIT_ASSERT(a.isIncomeExpense() == true);
+
+ a.setAccountType(MyMoneyAccount::AssetLoan);
+ CPPUNIT_ASSERT(a.isIncomeExpense() == false);
+
+ a.setAccountType(MyMoneyAccount::Stock);
+ CPPUNIT_ASSERT(a.isIncomeExpense() == false);
+
+ a.setAccountType(MyMoneyAccount::Equity);
+ CPPUNIT_ASSERT(a.isIncomeExpense() == false);
+}
+
+void MyMoneyAccountTest::testIsAssetLiability(void)
+{
+ MyMoneyAccount a;
+
+ a.setAccountType(MyMoneyAccount::UnknownAccountType);
+ CPPUNIT_ASSERT(a.isAssetLiability() == false);
+
+ a.setAccountType(MyMoneyAccount::Checkings);
+ CPPUNIT_ASSERT(a.isAssetLiability() == true);
+
+ a.setAccountType(MyMoneyAccount::Savings);
+ CPPUNIT_ASSERT(a.isAssetLiability() == true);
+
+ a.setAccountType(MyMoneyAccount::Cash);
+ CPPUNIT_ASSERT(a.isAssetLiability() == true);
+
+ a.setAccountType(MyMoneyAccount::CreditCard);
+ CPPUNIT_ASSERT(a.isAssetLiability() == true);
+
+ a.setAccountType(MyMoneyAccount::Loan);
+ CPPUNIT_ASSERT(a.isAssetLiability() == true);
+
+ a.setAccountType(MyMoneyAccount::CertificateDep);
+ CPPUNIT_ASSERT(a.isAssetLiability() == true);
+
+ a.setAccountType(MyMoneyAccount::Investment);
+ CPPUNIT_ASSERT(a.isAssetLiability() == true);
+
+ a.setAccountType(MyMoneyAccount::MoneyMarket);
+ CPPUNIT_ASSERT(a.isAssetLiability() == true);
+
+ a.setAccountType(MyMoneyAccount::Asset);
+ CPPUNIT_ASSERT(a.isAssetLiability() == true);
+
+ a.setAccountType(MyMoneyAccount::Liability);
+ CPPUNIT_ASSERT(a.isAssetLiability() == true);
+
+ a.setAccountType(MyMoneyAccount::Currency);
+ CPPUNIT_ASSERT(a.isAssetLiability() == true);
+
+ a.setAccountType(MyMoneyAccount::Income);
+ CPPUNIT_ASSERT(a.isAssetLiability() == false);
+
+ a.setAccountType(MyMoneyAccount::Expense);
+ CPPUNIT_ASSERT(a.isAssetLiability() == false);
+
+ a.setAccountType(MyMoneyAccount::AssetLoan);
+ CPPUNIT_ASSERT(a.isAssetLiability() == true);
+
+ a.setAccountType(MyMoneyAccount::Stock);
+ CPPUNIT_ASSERT(a.isAssetLiability() == true);
+
+ a.setAccountType(MyMoneyAccount::Equity);
+ CPPUNIT_ASSERT(a.isAssetLiability() == false);
+}
+
+void MyMoneyAccountTest::testIsLoan(void)
+{
+ MyMoneyAccount a;
+
+ a.setAccountType(MyMoneyAccount::UnknownAccountType);
+ CPPUNIT_ASSERT(a.isLoan() == false);
+
+ a.setAccountType(MyMoneyAccount::Checkings);
+ CPPUNIT_ASSERT(a.isLoan() == false);
+
+ a.setAccountType(MyMoneyAccount::Savings);
+ CPPUNIT_ASSERT(a.isLoan() == false);
+
+ a.setAccountType(MyMoneyAccount::Cash);
+ CPPUNIT_ASSERT(a.isLoan() == false);
+
+ a.setAccountType(MyMoneyAccount::CreditCard);
+ CPPUNIT_ASSERT(a.isLoan() == false);
+
+ a.setAccountType(MyMoneyAccount::Loan);
+ CPPUNIT_ASSERT(a.isLoan() == true);
+
+ a.setAccountType(MyMoneyAccount::CertificateDep);
+ CPPUNIT_ASSERT(a.isLoan() == false);
+
+ a.setAccountType(MyMoneyAccount::Investment);
+ CPPUNIT_ASSERT(a.isLoan() == false);
+
+ a.setAccountType(MyMoneyAccount::MoneyMarket);
+ CPPUNIT_ASSERT(a.isLoan() == false);
+
+ a.setAccountType(MyMoneyAccount::Asset);
+ CPPUNIT_ASSERT(a.isLoan() == false);
+
+ a.setAccountType(MyMoneyAccount::Liability);
+ CPPUNIT_ASSERT(a.isLoan() == false);
+
+ a.setAccountType(MyMoneyAccount::Currency);
+ CPPUNIT_ASSERT(a.isLoan() == false);
+
+ a.setAccountType(MyMoneyAccount::Income);
+ CPPUNIT_ASSERT(a.isLoan() == false);
+
+ a.setAccountType(MyMoneyAccount::Expense);
+ CPPUNIT_ASSERT(a.isLoan() == false);
+
+ a.setAccountType(MyMoneyAccount::AssetLoan);
+ CPPUNIT_ASSERT(a.isLoan() == true);
+
+ a.setAccountType(MyMoneyAccount::Stock);
+ CPPUNIT_ASSERT(a.isLoan() == false);
+
+ a.setAccountType(MyMoneyAccount::Equity);
+ CPPUNIT_ASSERT(a.isLoan() == false);
+}
+
diff --git a/kmymoney2/mymoney/mymoneyaccounttest.h b/kmymoney2/mymoney/mymoneyaccounttest.h
new file mode 100644
index 0000000..c2af080
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyaccounttest.h
@@ -0,0 +1,70 @@
+/***************************************************************************
+ mymoneyaccounttest.h
+ -------------------
+ copyright : (C) 2002 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 __MYMONEYACCOUNTTEST_H__
+#define __MYMONEYACCOUNTTEST_H__
+
+#include <cppunit/extensions/HelperMacros.h>
+
+#define private public
+#define protected public
+#include "mymoneyaccount.h"
+#undef private
+
+class MyMoneyAccountTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(MyMoneyAccountTest);
+ CPPUNIT_TEST(testEmptyConstructor);
+ CPPUNIT_TEST(testConstructor);
+ CPPUNIT_TEST(testSetFunctions);
+ CPPUNIT_TEST(testCopyConstructor);
+ CPPUNIT_TEST(testAssignmentConstructor);
+ CPPUNIT_TEST(testSubAccounts);
+ CPPUNIT_TEST(testEquality);
+ CPPUNIT_TEST(testWriteXML);
+ CPPUNIT_TEST(testReadXML);
+ CPPUNIT_TEST(testHasReferenceTo);
+ CPPUNIT_TEST(testAdjustBalance);
+ CPPUNIT_TEST(testSetClosed);
+ CPPUNIT_TEST(testIsIncomeExpense);
+ CPPUNIT_TEST(testIsAssetLiability);
+ CPPUNIT_TEST(testIsLoan);
+ CPPUNIT_TEST_SUITE_END();
+
+protected:
+ MyMoneyAccount *m;
+
+public:
+ MyMoneyAccountTest();
+ void setUp ();
+ void tearDown ();
+ void testEmptyConstructor();
+ void testConstructor();
+ void testSetFunctions();
+ void testCopyConstructor();
+ void testAssignmentConstructor();
+ void testSubAccounts();
+ void testEquality();
+ void testWriteXML();
+ void testReadXML();
+ void testHasReferenceTo();
+ void testAdjustBalance();
+ void testSetClosed();
+ void testIsIncomeExpense();
+ void testIsAssetLiability();
+ void testIsLoan();
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneybudget.cpp b/kmymoney2/mymoney/mymoneybudget.cpp
new file mode 100644
index 0000000..46b8ca8
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneybudget.cpp
@@ -0,0 +1,354 @@
+/***************************************************************************
+ mymoneybudget.cpp
+ -------------------
+ begin : Sun July 4 2004
+ copyright : (C) 2004-2005 by Ace Jones
+ email : acejones@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstring.h>
+#include <qdom.h>
+
+// ----------------------------------------------------------------------------
+// KDE Includes
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "mymoneybudget.h"
+
+const QStringList MyMoneyBudget::AccountGroup::kBudgetLevelText = QStringList::split(",","none,monthly,monthbymonth,yearly,invalid",true);
+const int BUDGET_VERSION = 2;
+
+bool MyMoneyBudget::AccountGroup::isZero(void) const
+{
+ return (!m_budgetsubaccounts && m_budgetlevel == eMonthly && balance().isZero());
+}
+
+void MyMoneyBudget::AccountGroup::convertToMonthly(void)
+{
+ MyMoneyBudget::PeriodGroup period;
+
+ switch(m_budgetlevel) {
+ case eYearly:
+ case eMonthByMonth:
+ period = *(m_periods.begin()); // make him monthly
+ period.setAmount(balance() / MyMoneyMoney(12,1));
+ clearPeriods();
+ addPeriod(period.startDate(), period);
+ break;
+ default:
+ break;
+ }
+ m_budgetlevel = eMonthly;
+}
+
+void MyMoneyBudget::AccountGroup::convertToYearly(void)
+{
+ MyMoneyBudget::PeriodGroup period;
+
+ switch(m_budgetlevel) {
+ case eMonthByMonth:
+ case eMonthly:
+ period = *(m_periods.begin()); // make him monthly
+ period.setAmount(totalBalance());
+ clearPeriods();
+ addPeriod(period.startDate(), period);
+ break;
+ default:
+ break;
+ }
+ m_budgetlevel = eYearly;
+}
+
+void MyMoneyBudget::AccountGroup::convertToMonthByMonth(void)
+{
+ MyMoneyBudget::PeriodGroup period;
+ QDate date;
+
+ switch(m_budgetlevel) {
+ case eMonthByMonth:
+ case eMonthly:
+ period = *(m_periods.begin());
+ period.setAmount(totalBalance() / MyMoneyMoney(12,1));
+ clearPeriods();
+ date = period.startDate();
+ for(int i = 0; i < 12; ++i) {
+ addPeriod(date, period);
+ date = date.addMonths(1);
+ period.setStartDate(date);
+ }
+ break;
+ default:
+ break;
+ }
+ m_budgetlevel = eYearly;
+}
+
+MyMoneyBudget::AccountGroup MyMoneyBudget::AccountGroup::operator += (const MyMoneyBudget::AccountGroup& _r)
+{
+ MyMoneyBudget::AccountGroup r(_r);
+
+ // make both operands based on the same budget level
+ if(m_budgetlevel != r.m_budgetlevel) {
+ if(m_budgetlevel == eMonthly) { // my budget is monthly
+ if(r.m_budgetlevel == eYearly) { // his his yearly
+ r.convertToMonthly();
+ } else if(r.m_budgetlevel == eMonthByMonth) { // his is month by month
+ convertToMonthByMonth();
+ }
+ } else if(m_budgetlevel == eYearly) { // my budget is yearly
+ if(r.m_budgetlevel == eMonthly) { // his is monthly
+ r.convertToYearly();
+ } else if(r.m_budgetlevel == eMonthByMonth) { // his is month by month
+ convertToMonthByMonth();
+ }
+ } else if(m_budgetlevel == eMonthByMonth) { // my budget is month by month
+ r.convertToMonthByMonth();
+ }
+ }
+
+ // now both budgets should be of the same type and we simply need
+ // to iterate over the period list and add the values
+ QMap<QDate, MyMoneyBudget::PeriodGroup> periods = m_periods;
+ QMap<QDate, MyMoneyBudget::PeriodGroup> rPeriods = r.m_periods;
+ QMap<QDate, MyMoneyBudget::PeriodGroup>::const_iterator it_p;
+ QMap<QDate, MyMoneyBudget::PeriodGroup>::const_iterator it_pr;
+ m_periods.clear();
+ it_p = periods.begin();
+ it_pr = rPeriods.begin();
+ QDate date = (*it_p).startDate();
+ while(it_p != periods.end()) {
+ MyMoneyBudget::PeriodGroup period = *it_p;
+ if(it_pr != rPeriods.end()) {
+ period.setAmount(period.amount() + (*it_pr).amount());
+ ++it_pr;
+ }
+ addPeriod(date, period);
+ date = date.addMonths(1);
+ ++it_p;
+ }
+ return *this;
+}
+
+bool MyMoneyBudget::AccountGroup::operator == (const AccountGroup &r) const
+{
+ return (m_id == r.m_id
+ && m_budgetlevel == r.m_budgetlevel
+ && m_budgetsubaccounts == r.m_budgetsubaccounts
+ && m_periods.keys() == r.m_periods.keys()
+ && m_periods.values() == r.m_periods.values());
+}
+
+MyMoneyBudget::MyMoneyBudget(void) :
+ m_name("Unconfigured Budget")
+{
+}
+
+MyMoneyBudget::MyMoneyBudget(const QString& _name) :
+ m_name(_name)
+{
+}
+
+MyMoneyBudget::MyMoneyBudget(const QDomElement& node) :
+ MyMoneyObject(node)
+{
+ if(!read(node))
+ clearId();
+}
+
+MyMoneyBudget::MyMoneyBudget(const QString& id, const MyMoneyBudget& budget)
+{
+ *this = budget;
+ m_id = id;
+}
+
+MyMoneyBudget::~MyMoneyBudget()
+{
+}
+
+bool MyMoneyBudget::operator == (const MyMoneyBudget& right) const
+{
+ return (MyMoneyObject::operator==(right) &&
+ (m_accounts.count() == right.m_accounts.count()) &&
+ (m_accounts.keys() == right.m_accounts.keys()) &&
+ (m_accounts.values() == right.m_accounts.values()) &&
+ (m_name == right.m_name) &&
+ (m_start == right.m_start) );
+}
+
+void MyMoneyBudget::write(QDomElement& e, QDomDocument *doc) const
+{
+ writeBaseXML(*doc, e);
+
+ e.setAttribute("name", m_name);
+ e.setAttribute("start", m_start.toString(Qt::ISODate) );
+ e.setAttribute("version", BUDGET_VERSION);
+
+ QMap<QString, AccountGroup>::const_iterator it;
+ for(it = m_accounts.begin(); it != m_accounts.end(); ++it) {
+ // only add the account if there is a budget entered
+ if(!(*it).balance().isZero()) {
+ QDomElement domAccount = doc->createElement("ACCOUNT");
+ domAccount.setAttribute("id", it.key());
+ domAccount.setAttribute("budgetlevel", AccountGroup::kBudgetLevelText[it.data().budgetLevel()]);
+ domAccount.setAttribute("budgetsubaccounts", it.data().budgetSubaccounts());
+
+ const QMap<QDate, PeriodGroup> periods = it.data().getPeriods();
+ QMap<QDate, PeriodGroup>::const_iterator it_per;
+ for(it_per = periods.begin(); it_per != periods.end(); ++it_per) {
+ if(!(*it_per).amount().isZero()) {
+ QDomElement domPeriod = doc->createElement("PERIOD");
+
+ domPeriod.setAttribute("amount", (*it_per).amount().toString());
+ domPeriod.setAttribute("start", (*it_per).startDate().toString(Qt::ISODate));
+ domAccount.appendChild(domPeriod);
+ }
+ }
+
+ e.appendChild(domAccount);
+ }
+ }
+}
+
+bool MyMoneyBudget::read(const QDomElement& e)
+{
+ // The goal of this reading method is 100% backward AND 100% forward
+ // compatability. Any Budget ever created with any version of KMyMoney
+ // should be able to be loaded by this method (as long as it's one of the
+ // Budget types supported in this version, of course)
+
+ bool result = false;
+
+ if ("BUDGET" == e.tagName())
+ {
+ result = true;
+ m_name = e.attribute("name");
+ m_start = QDate::fromString(e.attribute("start"), Qt::ISODate);
+ m_id = e.attribute("id");
+
+ QDomNode child = e.firstChild();
+ while(!child.isNull() && child.isElement())
+ {
+ QDomElement c = child.toElement();
+
+ AccountGroup account;
+
+ if("ACCOUNT" == c.tagName()) {
+ if(c.hasAttribute("id"))
+ account.setId(c.attribute("id"));
+
+ if(c.hasAttribute("budgetlevel")) {
+ int i = AccountGroup::kBudgetLevelText.findIndex(c.attribute("budgetlevel"));
+ if ( i != -1 )
+ account.setBudgetLevel(static_cast<AccountGroup::eBudgetLevel>(i));
+ }
+
+ if(c.hasAttribute("budgetsubaccounts"))
+ account.setBudgetSubaccounts(c.attribute("budgetsubaccounts").toUInt());
+ }
+
+ QDomNode period = c.firstChild();
+ while(!period.isNull() && period.isElement())
+ {
+ QDomElement per = period.toElement();
+ PeriodGroup pGroup;
+
+ if("PERIOD" == per.tagName() && per.hasAttribute("amount") && per.hasAttribute("start"))
+ {
+ pGroup.setAmount( MyMoneyMoney(per.attribute("amount")) );
+ pGroup.setStartDate( QDate::fromString(per.attribute("start"), Qt::ISODate) );
+ account.addPeriod(pGroup.startDate(), pGroup);
+ }
+
+ period = period.nextSibling();
+ }
+
+ m_accounts[account.id()] = account;
+
+ child = child.nextSibling();
+ }
+ }
+
+ return result;
+}
+
+void MyMoneyBudget::writeXML(QDomDocument& document, QDomElement& parent) const
+{
+ QDomElement el = document.createElement("BUDGET");
+ write(el,&document);
+ parent.appendChild(el);
+}
+
+bool MyMoneyBudget::hasReferenceTo(const QString& id) const
+{
+ // return true if we have an assignment for this id
+ return (m_accounts.contains(id));
+}
+
+void MyMoneyBudget::removeReference(const QString& id)
+{
+ if(m_accounts.contains(id)) {
+ m_accounts.remove(id);
+ }
+}
+
+void MyMoneyBudget::setAccount(const AccountGroup &_account, const QString _id)
+{
+ if(_account.isZero()) {
+ m_accounts.remove(_id);
+ } else {
+ // make sure we store a correct id
+ AccountGroup account(_account);
+ if(account.id() != _id)
+ account.setId(_id);
+ m_accounts[_id] = account;
+ }
+}
+
+const MyMoneyBudget::AccountGroup& MyMoneyBudget::account(const QString _id) const
+{
+ static AccountGroup empty;
+
+ if ( m_accounts.contains(_id) )
+ return m_accounts[_id];
+ return empty;
+}
+
+void MyMoneyBudget::setBudgetStart(const QDate& _start)
+{
+ QDate oldDate = QDate(m_start.year(), m_start.month(), 1);
+ m_start = QDate(_start.year(), _start.month(), 1);
+ if(oldDate.isValid()) {
+ int adjust = ((m_start.year() - oldDate.year())*12) + (m_start.month() - oldDate.month());
+ QMap<QString, AccountGroup>::iterator it;
+ for(it = m_accounts.begin(); it != m_accounts.end(); ++it) {
+ const QMap<QDate, PeriodGroup> periods = (*it).getPeriods();
+ QMap<QDate, PeriodGroup>::const_iterator it_per;
+ (*it).clearPeriods();
+ for(it_per = periods.begin(); it_per != periods.end(); ++it_per) {
+ PeriodGroup pgroup = (*it_per);
+ pgroup.setStartDate(pgroup.startDate().addMonths(adjust));
+ (*it).addPeriod(pgroup.startDate(), pgroup);
+ }
+ }
+ }
+}
+
+// vim:cin:si:ai:et:ts=2:sw=2:
diff --git a/kmymoney2/mymoney/mymoneybudget.h b/kmymoney2/mymoney/mymoneybudget.h
new file mode 100644
index 0000000..a2eea02
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneybudget.h
@@ -0,0 +1,269 @@
+/***************************************************************************
+ mymoneybudget.h
+ -------------------
+ begin : Sun Jan 22 2006
+ copyright : (C) 2006 by Darren Gould
+ email : darren_gould@gmx.de
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYBUDGET_H
+#define MYMONEYBUDGET_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qmap.h>
+#include <qvaluelist.h>
+#include <qstring.h>
+class QDomElement;
+class QDomDocument;
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include <kmymoney/mymoneyobject.h>
+#include <kmymoney/mymoneyaccount.h>
+#include <kmymoney/mymoneymoney.h>
+#include <kmymoney/export.h>
+
+/**
+ * This class defines a Budget within the MyMoneyEngine. The Budget class
+ * contains all the configuration parameters needed to run a Budget, plus
+ * XML serialization.
+ *
+ * As noted above, this class only provides a Budget DEFINITION. The
+ * generation and presentation of the Budget itself are left to higher
+ * level classes.
+ *
+ * @author Darren Gould <darren_gould@gmx.de>
+ */
+class KMYMONEY_EXPORT MyMoneyBudget: public MyMoneyObject
+{
+public:
+ MyMoneyBudget(void);
+ ~MyMoneyBudget();
+ MyMoneyBudget(const QString& _name);
+ /**
+ * This constructor creates an object based on the data found in the
+ * QDomElement referenced by @p node. If problems arise, the @p id of
+ * the object is cleared (see MyMoneyObject::clearId()).
+ */
+ MyMoneyBudget(const QDomElement& node);
+
+ /**
+ * This constructor creates an object based on the data found in the
+ * MyMoneyBudget budget object.
+ */
+ MyMoneyBudget(const QString& id, const MyMoneyBudget& budget);
+
+ /**
+ * Helper class for MyMoneyBudget
+ *
+ * This is an abstraction of the PERIOD stored in the BUDGET/ACCOUNT tag in XML
+ *
+ * @author Darren Gould
+ */
+ class PeriodGroup
+ {
+ public:
+ // get functions
+ const QDate& startDate ( void ) const { return m_start; }
+ const MyMoneyMoney& amount( void ) const { return m_amount; }
+
+ // set functions
+ void setStartDate ( const QDate& _start ) { m_start = _start; }
+ void setAmount( const MyMoneyMoney& _amount ) { m_amount = _amount;}
+
+ bool operator == (const PeriodGroup &r) const { return (m_start == r.m_start && m_amount == r.m_amount); }
+
+ private:
+ QDate m_start;
+ MyMoneyMoney m_amount;
+ };
+
+ /**
+ * Helper class for MyMoneyBudget
+ *
+ * This is an abstraction of the Account Data stored in the BUDGET tag in XML
+ *
+ * @author Darren Gould
+ */
+ class AccountGroup
+ {
+ public:
+ typedef enum
+ {
+ eNone = 0,
+ eMonthly,
+ eMonthByMonth,
+ eYearly,
+ eMax
+ } eBudgetLevel;
+
+ static const QStringList kBudgetLevelText;
+
+ public:
+ AccountGroup() : m_budgetlevel(eNone), m_budgetsubaccounts(false) {}
+
+ // get functions
+ const QString& id( void ) const { return m_id; }
+ bool budgetSubaccounts( void ) const { return m_budgetsubaccounts; }
+ eBudgetLevel budgetLevel( void ) const { return m_budgetlevel; }
+ const PeriodGroup& period( const QDate &_date ) const { return m_periods[_date]; }
+ const QMap<QDate, PeriodGroup>& getPeriods( void ) const { return m_periods; }
+ void clearPeriods(void) { m_periods.clear(); }
+ const MyMoneyMoney balance( void ) const
+ {
+ MyMoneyMoney balance;
+
+ QMap<QDate, PeriodGroup>::const_iterator it;
+ for(it = m_periods.begin(); it != m_periods.end(); ++it)
+ {
+ balance += (*it).amount();
+ }
+ return balance;
+ };
+
+ const MyMoneyMoney totalBalance(void) const
+ {
+ MyMoneyMoney bal = balance();
+ switch(m_budgetlevel) {
+ default:
+ break;
+ case eMonthly:
+ bal = bal * 12;
+ break;
+ }
+ return bal;
+ }
+
+ // set functions
+ void setId( QString _id ) { m_id = _id; }
+ void setBudgetLevel( eBudgetLevel _level ) { m_budgetlevel = _level; }
+ void setBudgetSubaccounts( bool _b ) { m_budgetsubaccounts = _b; }
+ void addPeriod( const QDate& _date, PeriodGroup &period ) { m_periods[_date] = period; }
+
+ // This member adds the value of another account group
+ // m_budgetlevel is adjusted to the larger one of both
+ // m_budgetsubaccounts remains unaffected
+ AccountGroup operator += (const AccountGroup& r);
+
+ bool operator == (const AccountGroup &r) const;
+
+ bool isZero(void) const;
+
+ protected:
+ void convertToMonthly(void);
+ void convertToYearly(void);
+ void convertToMonthByMonth(void);
+
+ private:
+ QString m_id;
+
+ eBudgetLevel m_budgetlevel;
+ bool m_budgetsubaccounts;
+ QMap<QDate, PeriodGroup> m_periods;
+ };
+
+ /**
+ * This operator tests for equality of two MyMoneyBudget objects
+ */
+ bool operator == (const MyMoneyBudget &) const;
+
+ // Simple get operations
+ const QString& name(void) const { return m_name; }
+ const QDate& budgetStart(void) const { return m_start; }
+ QString id(void) const { return m_id; }
+ const AccountGroup & account(const QString _id) const;
+ bool contains(const QString _id) const { return m_accounts.contains(_id); }
+ QValueList<AccountGroup> getaccounts(void) const { return m_accounts.values(); }
+
+ // Simple set operations
+ void setName(const QString& _name) { m_name = _name; }
+ void setBudgetStart(const QDate& _start);
+ void setAccount(const AccountGroup &_account, const QString _id);
+
+ /**
+ * This method writes this Budget to the DOM element @p e,
+ * within the DOM document @p doc.
+ *
+ * @param e The element which should be populated with info from this Budget
+ * @param doc The document which we can use to create new sub-elements
+ * if needed
+ */
+ void write(QDomElement& e, QDomDocument *doc) const;
+
+ /**
+ * This method reads a Budget from the DOM element @p e, and
+ * populates this Budget with the results.
+ *
+ * @param e The element from which the Budget should be read
+ *
+ * @return bool True if a Budget was successfully loaded from the
+ * element @p e. If false is returned, the contents of this Budget
+ * object are undefined.
+ */
+ bool read(const QDomElement& e);
+
+ /**
+ * This method creates a QDomElement for the @p document
+ * under the parent node @p parent. (This version overwrites the
+ * MMObject base class.)
+ *
+ * @param document reference to QDomDocument
+ * @param parent reference to QDomElement parent node
+ */
+ virtual void writeXML(QDomDocument& document, QDomElement& parent) const;
+
+ /**
+ * This method checks if a reference to the given object exists. It returns,
+ * a @p true if the object is referencing the one requested by the
+ * parameter @p id and the balance() returned is zero.
+ * If it does not, this method returns @p false.
+ *
+ * @param id id of the object to be checked for references
+ * @retval true This object references object with id @p id.
+ * @retval false This object does not reference the object with id @p id.
+ */
+ virtual bool hasReferenceTo(const QString& id) const;
+
+ /**
+ * This member removes all references to object identified by @p id. Used
+ * to remove objects which are about to be removed from the engine.
+ */
+ void removeReference(const QString& id);
+
+private:
+ /**
+ * The user-assigned name of the Budget
+ */
+ QString m_name;
+
+ /**
+ * The user-assigned year of the Budget
+ */
+ QDate m_start;
+
+ /**
+ * Map the budgeted accounts
+ *
+ * Each account Id is stored against the AccountGroup information
+ */
+ QMap<QString, AccountGroup> m_accounts;
+};
+
+#endif // MYMONEYBudget_H
+// vim:cin:si:ai:et:ts=2:sw=2:
diff --git a/kmymoney2/mymoney/mymoneycategory.cpp b/kmymoney2/mymoney/mymoneycategory.cpp
new file mode 100644
index 0000000..5bf55ab
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneycategory.cpp
@@ -0,0 +1,170 @@
+/***************************************************************************
+ mymoneycategory.cpp
+ -------------------
+ copyright : (C) 2000 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "mymoneycategory.h"
+
+MyMoneyCategory::MyMoneyCategory()
+{
+ m_income = true;
+}
+
+MyMoneyCategory::MyMoneyCategory(const bool income, const QString name)
+{
+ m_income = income;
+ m_name = name;
+}
+
+MyMoneyCategory::MyMoneyCategory(const bool income, const QString name, QStringList minors)
+{
+ m_income = income;
+ m_name = name;
+ m_minorCategories = minors;
+}
+
+MyMoneyCategory::~MyMoneyCategory()
+{
+}
+
+// Functions use the find method to search the list
+bool MyMoneyCategory::addMinorCategory(const QString val)
+{
+ if (val.isEmpty() || val.isNull())
+ return false;
+
+ if (m_minorCategories.find(val) == m_minorCategories.end()) {
+ m_minorCategories.append(val);
+ return true;
+ }
+
+ return false;
+}
+
+bool MyMoneyCategory::removeMinorCategory(const QString val)
+{
+ if (val.isEmpty() || val.isNull())
+ return false;
+
+ if (m_minorCategories.find(val) != m_minorCategories.end()) {
+ m_minorCategories.remove(val);
+ return true;
+ }
+
+ return false;
+}
+
+bool MyMoneyCategory::renameMinorCategory(const QString oldVal, const QString newVal)
+{
+ if (oldVal.isEmpty() || oldVal.isNull() || newVal.isEmpty() || newVal.isNull())
+ return false;
+
+ if (m_minorCategories.find(oldVal) != m_minorCategories.end() &&
+ m_minorCategories.find(newVal) == m_minorCategories.end() ) {
+
+ m_minorCategories.remove(oldVal);
+ return addMinorCategory(newVal);
+ }
+
+ return false;
+}
+
+bool MyMoneyCategory::addMinorCategory(QStringList values)
+{
+ for (QStringList::Iterator it = values.begin(); it!=values.end(); ++it) {
+ addMinorCategory(*it);
+ }
+
+ return true;
+}
+
+bool MyMoneyCategory::setMinorCategories(QStringList values)
+{
+ m_minorCategories.clear();
+ return addMinorCategory(values);
+}
+
+bool MyMoneyCategory::removeAllMinors(void)
+{
+ m_minorCategories.clear();
+ return true;
+}
+
+QString MyMoneyCategory::firstMinor(void)
+{
+ return m_minorCategories.first();
+}
+
+MyMoneyCategory::MyMoneyCategory(const MyMoneyCategory& right)
+{
+ m_income = right.m_income;
+ m_name = right.m_name;
+ m_minorCategories.clear();
+ m_minorCategories = right.m_minorCategories;
+}
+
+MyMoneyCategory& MyMoneyCategory::operator = (const MyMoneyCategory& right)
+{
+ m_income = right.m_income;
+ m_name = right.m_name;
+ m_minorCategories.clear();
+ m_minorCategories = right.m_minorCategories;
+ return *this;
+}
+
+QDataStream &operator<<(QDataStream &s, MyMoneyCategory &category)
+{
+ if (category.m_income)
+ s << (Q_INT32)1;
+ else
+ s << (Q_INT32)0;
+
+ s << category.m_name;
+
+ s << (Q_UINT32)category.m_minorCategories.count();
+ for (QStringList::Iterator it = category.m_minorCategories.begin(); it!=category.m_minorCategories.end(); ++it) {
+ s << (*it);
+ }
+
+ return s;
+}
+
+QDataStream &operator>>(QDataStream &s, MyMoneyCategory &category)
+{
+ Q_INT32 inc;
+ s >> inc;
+ if (inc==0)
+ category.m_income = false;
+ else
+ category.m_income = true;
+
+ s >> category.m_name;
+
+ Q_UINT32 minorCount;
+ QString buffer;
+
+ s >> minorCount;
+ category.m_minorCategories.clear();
+ for (unsigned int i=0; i<minorCount; i++) {
+ s >> buffer;
+ category.m_minorCategories.append(buffer);
+ }
+
+ return s;
+}
+
+void MyMoneyCategory::clear(void)
+{
+ m_minorCategories.clear();
+}
diff --git a/kmymoney2/mymoney/mymoneycategory.h b/kmymoney2/mymoney/mymoneycategory.h
new file mode 100644
index 0000000..3f4babc
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneycategory.h
@@ -0,0 +1,68 @@
+/***************************************************************************
+ mymoneycategory.h
+ -------------------
+ copyright : (C) 2000 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYCATEGORY_H
+#define MYMONEYCATEGORY_H
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstring.h>
+#include <qstringlist.h>
+
+/**
+ * @deprecated This class represents an Income or Expense category. Please don't
+ * use it anymore, as it will be removed sooner or later.
+ */
+class MyMoneyCategory {
+ bool m_income; // if false, m_income == expense
+ QString m_name;
+ QStringList m_minorCategories;
+
+ friend QDataStream &operator<<(QDataStream &, MyMoneyCategory &);
+ friend QDataStream &operator>>(QDataStream &, MyMoneyCategory &);
+
+public:
+ MyMoneyCategory();
+ MyMoneyCategory(const bool income, const QString name);
+ MyMoneyCategory(const bool income, const QString name, QStringList minors);
+ ~MyMoneyCategory();
+
+ // Simple get operations
+ QString name(void) { return m_name; }
+ QStringList& minorCategories(void) { return m_minorCategories; }
+
+ // Simple set operations
+ bool isIncome(void) { return m_income; }
+ void setIncome(const bool val) { m_income = val; }
+ void setName(const QString val) { m_name = val; }
+
+ bool setMinorCategories(QStringList values);
+ bool addMinorCategory(const QString val);
+ bool removeMinorCategory(const QString val);
+ bool renameMinorCategory(const QString oldVal, const QString newVal);
+ bool addMinorCategory(QStringList values);
+ bool removeAllMinors(void);
+ QString firstMinor(void);
+
+ void clear(void);
+
+ // Copy constructors
+ MyMoneyCategory(const MyMoneyCategory&);
+ MyMoneyCategory& operator = (const MyMoneyCategory&);
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneyexception.cpp b/kmymoney2/mymoney/mymoneyexception.cpp
new file mode 100644
index 0000000..6b61e67
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyexception.cpp
@@ -0,0 +1,35 @@
+/***************************************************************************
+ mymoneyexception.cpp - description
+ -------------------
+ begin : Sun Apr 28 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "mymoneyexception.h"
+
+MyMoneyException::MyMoneyException(const QString& msg, const QString& file, const unsigned long line)
+{
+ // qDebug("MyMoneyException(%s,%s,%d)", msg.data(), file.data(), line);
+ m_msg = msg;
+ m_file = file;
+ m_line = line;
+}
+
+MyMoneyException::~MyMoneyException()
+{
+}
diff --git a/kmymoney2/mymoney/mymoneyexception.h b/kmymoney2/mymoney/mymoneyexception.h
new file mode 100644
index 0000000..68cf2af
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyexception.h
@@ -0,0 +1,115 @@
+/***************************************************************************
+ mymoneyexception.h - description
+ -------------------
+ begin : Sun Apr 28 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYEXCEPTION_H
+#define MYMONEYEXCEPTION_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstring.h>
+#include <kmymoney/export.h>
+/**
+ * @file
+ * @author Thomas Baumgart
+ */
+
+/**
+ * This class describes an exception that is thrown by the engine
+ * in case of a failure.
+ */
+class KMYMONEY_EXPORT MyMoneyException {
+public:
+
+/**
+ * @def MYMONEYEXCEPTION(text)
+ * This is the preferred constructor to create a new exception
+ * object. It automatically inserts the filename and the source
+ * code line into the object upon creation.
+ *
+ * It is equivilant to MyMoneyException(text, __FILE__, __LINE__)
+ */
+#define MYMONEYEXCEPTION(a) MyMoneyException(a, __FILE__, __LINE__)
+
+ /**
+ * The constructor to create a new MyMoneyException object.
+ *
+ * @param msg reference to QString containing the message
+ * @param file reference to QString containing the name of the sourcefile where
+ * the exception was thrown
+ * @param line unsigned long containing the line number of the line where
+ * the exception was thrown in the file.
+ *
+ * An easier way to use this constructor is to use the macro
+ * MYMONEYEXCEPTION(text) instead. It automatically assigns the file
+ * and line parameter to the correct values.
+ */
+ MyMoneyException(const QString& msg, const QString& file, const unsigned long line);
+
+ ~MyMoneyException();
+
+ /**
+ * This method is used to return the message that was passed
+ * during the creation of the exception object.
+ *
+ * @return reference to QString containing the message
+ */
+ const QString& what(void) const { return m_msg; };
+
+ /**
+ * This method is used to return the filename that was passed
+ * during the creation of the exception object.
+ *
+ * @return reference to QString containing the filename
+ */
+ const QString& file(void) const { return m_file; };
+
+ /**
+ * This method is used to return the linenumber that was passed
+ * during the creation of the exception object.
+ *
+ * @return long integer containing the line number
+ */
+ unsigned long line(void) const { return m_line; };
+
+private:
+ /**
+ * This member variable holds the message
+ */
+ QString m_msg;
+
+ /**
+ * This member variable holds the filename
+ */
+ QString m_file;
+
+ /**
+ * This member variable holds the line number
+ */
+ unsigned long m_line;
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneyexceptiontest.cpp b/kmymoney2/mymoney/mymoneyexceptiontest.cpp
new file mode 100644
index 0000000..9d036ff
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyexceptiontest.cpp
@@ -0,0 +1,51 @@
+
+/***************************************************************************
+ mymoneyexceptiontest.cpp
+ -------------------
+ copyright : (C) 2002 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "mymoneyexceptiontest.h"
+
+MyMoneyExceptionTest::MyMoneyExceptionTest()
+{
+}
+
+
+void MyMoneyExceptionTest::setUp()
+{
+}
+
+void MyMoneyExceptionTest::tearDown()
+{
+}
+
+void MyMoneyExceptionTest::testDefaultConstructor()
+{
+ MyMoneyException *e = new MYMONEYEXCEPTION("Message");
+ CPPUNIT_ASSERT(e->what() == "Message");
+ CPPUNIT_ASSERT(e->line() == __LINE__-2);
+ CPPUNIT_ASSERT(e->file() == __FILE__);
+ delete e;
+}
+
+void MyMoneyExceptionTest::testConstructor()
+{
+ MyMoneyException *e = new MyMoneyException("New message",
+ "Joe's file", 1234);
+ CPPUNIT_ASSERT(e->what() == "New message");
+ CPPUNIT_ASSERT(e->line() == 1234);
+ CPPUNIT_ASSERT(e->file() == "Joe's file");
+ delete e;
+}
+
diff --git a/kmymoney2/mymoney/mymoneyexceptiontest.h b/kmymoney2/mymoney/mymoneyexceptiontest.h
new file mode 100644
index 0000000..95dbe48
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyexceptiontest.h
@@ -0,0 +1,47 @@
+/***************************************************************************
+ mymoneyexceptiontest.h
+ -------------------
+ copyright : (C) 2002 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 __MYMONEYEXCEPTIONTEST_H__
+#define __MYMONEYEXCEPTIONTEST_H__
+
+#include <cppunit/extensions/HelperMacros.h>
+
+#define private public
+#include "mymoneyutils.h"
+#include "mymoneyexception.h"
+#undef private
+
+class MyMoneyExceptionTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(MyMoneyExceptionTest);
+ CPPUNIT_TEST(testDefaultConstructor);
+ CPPUNIT_TEST(testConstructor);
+ CPPUNIT_TEST_SUITE_END();
+
+protected:
+public:
+ MyMoneyExceptionTest();
+
+
+ void setUp();
+
+ void tearDown();
+
+ void testDefaultConstructor();
+
+ void testConstructor();
+
+};
+#endif
diff --git a/kmymoney2/mymoney/mymoneyfile.cpp b/kmymoney2/mymoney/mymoneyfile.cpp
new file mode 100644
index 0000000..3245746
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyfile.cpp
@@ -0,0 +1,2332 @@
+/***************************************************************************
+ mymoneyfile.cpp
+ -------------------
+ copyright : (C) 2000 by Michael Edwardes
+ (C) 2002, 2007-2008 by Thomas Baumgart
+ email : mte@users.sourceforge.net
+ ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstring.h>
+#include <qdatetime.h>
+#include <qvaluelist.h>
+
+// ----------------------------------------------------------------------------
+// KDE Includes
+
+#include <kdebug.h>
+#include <klocale.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+#include "storage/mymoneyseqaccessmgr.h"
+#include "mymoneyfile.h"
+#include "mymoneyreport.h"
+#include "mymoneybudget.h"
+#include "mymoneyprice.h"
+#include "mymoneyobjectcontainer.h"
+
+#ifndef HAVE_CONFIG_H
+#define VERSION "UNKNOWN"
+#else
+#include "config.h"
+#endif
+
+const QString MyMoneyFile::OpeningBalancesPrefix = I18N_NOOP("Opening Balances");
+const QString MyMoneyFile::AccountSeperator = ":";
+
+// include the following line to get a 'cout' for debug purposes
+// #include <iostream>
+MyMoneyFile* MyMoneyFile::_instance = 0;
+
+class MyMoneyFile::Private
+{
+public:
+ Private() :
+ m_inTransaction(false)
+ {}
+
+ bool m_inTransaction;
+ MyMoneySecurity m_baseCurrency;
+ MyMoneyObjectContainer m_cache;
+ MyMoneyPriceList m_priceCache;
+
+ /**
+ * This member keeps a list of ids to notify after an
+ * operation is completed. The boolean is used as follows
+ * during processing of the list:
+ *
+ * false - don't reload the object immediately
+ * true - reload the object immediately
+ */
+ QMap<QString, bool> m_notificationList;
+
+};
+
+MyMoneyFile MyMoneyFile::file;
+
+MyMoneyFile::MyMoneyFile() :
+ d(new Private)
+{
+ m_storage = 0;
+}
+
+MyMoneyFile::~MyMoneyFile()
+{
+ _instance = 0;
+ delete m_storage;
+ delete d;
+}
+
+MyMoneyFile::MyMoneyFile(IMyMoneyStorage *storage) :
+ d(new Private)
+{
+ m_storage = 0;
+ attachStorage(storage);
+}
+
+void MyMoneyFile::attachStorage(IMyMoneyStorage* const storage)
+{
+ if(m_storage != 0)
+ throw new MYMONEYEXCEPTION("Storage already attached");
+
+ if(storage == 0)
+ throw new MYMONEYEXCEPTION("Storage must not be 0");
+
+ m_storage = storage;
+
+ // force reload of base currency
+ d->m_baseCurrency = MyMoneySecurity();
+
+ // and the whole cache
+ d->m_cache.clear(storage);
+ d->m_priceCache.clear();
+ preloadCache();
+
+ // notify application about new data availability
+ emit dataChanged();
+}
+
+void MyMoneyFile::detachStorage(IMyMoneyStorage* const /* storage */)
+{
+ d->m_cache.clear();
+ d->m_priceCache.clear();
+ m_storage = 0;
+}
+
+void MyMoneyFile::startTransaction(void)
+{
+ checkStorage();
+ if(d->m_inTransaction) {
+ throw new MYMONEYEXCEPTION("Already started a transaction!");
+ }
+
+ m_storage->startTransaction();
+ d->m_inTransaction = true;
+}
+
+bool MyMoneyFile::hasTransaction(void) const
+{
+ return d->m_inTransaction;
+}
+
+void MyMoneyFile::checkTransaction(const char* txt) const
+{
+ checkStorage();
+ if(!d->m_inTransaction) {
+ throw new MYMONEYEXCEPTION(QString("No transaction started for %1").arg(txt));
+ }
+}
+
+void MyMoneyFile::commitTransaction(void)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ bool changed = m_storage->commitTransaction();
+ d->m_inTransaction = false;
+ preloadCache();
+ if(changed) {
+ emit dataChanged();
+ }
+}
+
+void MyMoneyFile::rollbackTransaction(void)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ m_storage->rollbackTransaction();
+ d->m_inTransaction = false;
+ preloadCache();
+}
+
+void MyMoneyFile::addInstitution(MyMoneyInstitution& institution)
+{
+ // perform some checks to see that the institution stuff is OK. For
+ // now we assume that the institution must have a name, the ID is not set
+ // and it does not have a parent (MyMoneyFile).
+
+ if(institution.name().length() == 0
+ || institution.id().length() != 0)
+ throw new MYMONEYEXCEPTION("Not a new institution");
+
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->addInstitution(institution);
+
+ d->m_cache.preloadInstitution(institution);
+}
+
+void MyMoneyFile::modifyInstitution(const MyMoneyInstitution& institution)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->modifyInstitution(institution);
+
+ addNotification(institution.id());
+}
+
+void MyMoneyFile::modifyTransaction(const MyMoneyTransaction& transaction)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ const MyMoneyTransaction* t = &transaction;
+ MyMoneyTransaction tCopy;
+
+ // now check the splits
+ bool loanAccountAffected = false;
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+ for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
+ // the following line will throw an exception if the
+ // account does not exist
+ MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());
+ if(acc.id().isEmpty())
+ throw new MYMONEYEXCEPTION("Cannot store split with no account assigned");
+ if(isStandardAccount((*it_s).accountId()))
+ throw new MYMONEYEXCEPTION("Cannot store split referencing standard account");
+ if(acc.isLoan() && ((*it_s).action() == MyMoneySplit::ActionTransfer))
+ loanAccountAffected = true;
+ }
+
+ // change transfer splits between asset/liability and loan accounts
+ // into amortization splits
+ if(loanAccountAffected) {
+ tCopy = transaction;
+ QValueList<MyMoneySplit> list = transaction.splits();
+ for(it_s = list.begin(); it_s != list.end(); ++it_s) {
+ if((*it_s).action() == MyMoneySplit::ActionTransfer) {
+ MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());
+
+ if(acc.isAssetLiability()) {
+ MyMoneySplit s = (*it_s);
+ s.setAction(MyMoneySplit::ActionAmortization);
+ tCopy.modifySplit(s);
+ t = &tCopy;
+ }
+ }
+ }
+ }
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ // get the current setting of this transaction
+ MyMoneyTransaction tr = MyMoneyFile::transaction(transaction.id());
+
+ // scan the splits again to update notification list
+ // and mark all accounts that are referenced
+ for(it_s = tr.splits().begin(); it_s != tr.splits().end(); ++it_s) {
+ addNotification((*it_s).accountId());
+ addNotification((*it_s).payeeId());
+ }
+
+ // perform modification
+ m_storage->modifyTransaction(*t);
+
+ // and mark all accounts that are referenced
+ for(it_s = t->splits().begin(); it_s != t->splits().end(); ++it_s) {
+ addNotification((*it_s).accountId());
+ addNotification((*it_s).payeeId());
+ }
+}
+
+void MyMoneyFile::modifyAccount(const MyMoneyAccount& _account)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ MyMoneyAccount account(_account);
+
+ MyMoneyAccount acc = MyMoneyFile::account(account.id());
+
+ // check that for standard accounts only specific parameters are changed
+ if(isStandardAccount(account.id())) {
+ // make sure to use the stuff we found on file
+ account = acc;
+
+ // and only use the changes that are allowed
+ account.setName(_account.name());
+ account.setCurrencyId(_account.currencyId());
+
+ // now check that it is the same
+ if(!(account == _account))
+ throw new MYMONEYEXCEPTION("Unable to modify the standard account groups");
+ }
+
+ if(account.accountType() != acc.accountType())
+ throw new MYMONEYEXCEPTION("Unable to change account type");
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ // if the account was moved to another insitution, we notify
+ // the old one as well as the new one and the structure change
+ if(acc.institutionId() != account.institutionId()) {
+ MyMoneyInstitution inst;
+ if(!acc.institutionId().isEmpty()) {
+ inst = institution(acc.institutionId());
+ inst.removeAccountId(acc.id());
+ modifyInstitution(inst);
+ }
+ if(!account.institutionId().isEmpty()) {
+ inst = institution(account.institutionId());
+ inst.addAccountId(acc.id());
+ modifyInstitution(inst);
+ }
+ addNotification(acc.institutionId());
+ addNotification(account.institutionId());
+ }
+
+ m_storage->modifyAccount(account);
+
+ addNotification(account.id());
+}
+
+void MyMoneyFile::reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // check that it's not one of the standard account groups
+ if(isStandardAccount(account.id()))
+ throw new MYMONEYEXCEPTION("Unable to reparent the standard account groups");
+
+ if(account.accountGroup() == parent.accountGroup()
+ || (account.accountType() == MyMoneyAccount::Income && parent.accountType() == MyMoneyAccount::Expense)
+ || (account.accountType() == MyMoneyAccount::Expense && parent.accountType() == MyMoneyAccount::Income)) {
+
+ if(account.isInvest() && parent.accountType() != MyMoneyAccount::Investment)
+ throw new MYMONEYEXCEPTION("Unable to reparent Stock to non-investment account");
+
+ if(parent.accountType() == MyMoneyAccount::Investment && !account.isInvest())
+ throw new MYMONEYEXCEPTION("Unable to reparent non-stock to investment account");
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ // keep a notification of the current parent
+ addNotification(account.parentAccountId());
+
+ m_storage->reparentAccount(account, parent);
+
+ // and also keep one for the account itself and the new parent
+ addNotification(account.id());
+ addNotification(parent.id());
+
+ } else
+ throw new MYMONEYEXCEPTION("Unable to reparent to different account type");
+}
+
+const MyMoneyInstitution& MyMoneyFile::institution(const QString& id) const
+{
+ return d->m_cache.institution(id);
+}
+
+const MyMoneyAccount& MyMoneyFile::account(const QString& id) const
+{
+ return d->m_cache.account(id);
+}
+
+const MyMoneyAccount& MyMoneyFile::subAccountByName(const MyMoneyAccount& acc, const QString& name) const
+{
+ static MyMoneyAccount nullAccount;
+
+ QValueList<QString>::const_iterator it_a;
+ for(it_a = acc.accountList().begin(); it_a != acc.accountList().end(); ++it_a) {
+ const MyMoneyAccount& sacc = account(*it_a);
+ if(sacc.name() == name)
+ return sacc;
+ }
+ return nullAccount;
+}
+
+const MyMoneyAccount& MyMoneyFile::accountByName(const QString& name) const
+{
+ return d->m_cache.accountByName(name);
+}
+
+void MyMoneyFile::removeTransaction(const MyMoneyTransaction& transaction)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ // get the engine's idea about this transaction
+ MyMoneyTransaction tr = MyMoneyFile::transaction(transaction.id());
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+
+ // scan the splits again to update notification list
+ for(it_s = tr.splits().begin(); it_s != tr.splits().end(); ++it_s) {
+ MyMoneyAccount acc = account((*it_s).accountId());
+ if(acc.isClosed())
+ throw new MYMONEYEXCEPTION(i18n("Cannot remove transaction that references a closed account."));
+ addNotification((*it_s).accountId());
+ addNotification((*it_s).payeeId());
+ }
+
+ m_storage->removeTransaction(transaction);
+}
+
+
+bool MyMoneyFile::hasActiveSplits(const QString& id) const
+{
+ checkStorage();
+
+ return m_storage->hasActiveSplits(id);
+}
+
+bool MyMoneyFile::isStandardAccount(const QString& id) const
+{
+ checkStorage();
+
+ return m_storage->isStandardAccount(id);
+}
+
+void MyMoneyFile::setAccountName(const QString& id, const QString& name) const
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ m_storage->setAccountName(id, name);
+}
+
+void MyMoneyFile::removeAccount(const MyMoneyAccount& account)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ MyMoneyAccount parent;
+ MyMoneyAccount acc;
+ MyMoneyInstitution institution;
+
+ // check that the account and its parent exist
+ // this will throw an exception if the id is unknown
+ acc = MyMoneyFile::account(account.id());
+ parent = MyMoneyFile::account(account.parentAccountId());
+ if(!acc.institutionId().isEmpty())
+ institution = MyMoneyFile::institution(acc.institutionId());
+
+ // check that it's not one of the standard account groups
+ if(isStandardAccount(account.id()))
+ throw new MYMONEYEXCEPTION("Unable to remove the standard account groups");
+
+ if(hasActiveSplits(account.id())) {
+ throw new MYMONEYEXCEPTION("Unable to remove account with active splits");
+ }
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ // collect all sub-ordinate accounts for notification
+ QStringList::ConstIterator it;
+ for(it = acc.accountList().begin(); it != acc.accountList().end(); ++it)
+ addNotification(*it);
+ // don't forget the parent and a possible institution
+ addNotification(parent.id());
+ addNotification(account.institutionId());
+
+ if(!institution.id().isEmpty()) {
+ institution.removeAccountId(account.id());
+ m_storage->modifyInstitution(institution);
+ }
+ acc.setInstitutionId(QString());
+
+ m_storage->removeAccount(acc);
+ addNotification(acc.id(), false);
+ d->m_cache.clear(acc.id());
+}
+
+void MyMoneyFile::removeAccountList(const QStringList& account_list, unsigned int level) {
+ if (level > 100)
+ throw new MYMONEYEXCEPTION("Too deep recursion in [MyMoneyFile::removeAccountList]!");
+
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // upon entry, we check that we could proceed with the operation
+ if(!level) {
+ if(!hasOnlyUnusedAccounts(account_list, 0))
+ throw new MYMONEYEXCEPTION("One or more accounts cannot be removed");
+
+ // NOTE: We don't use a MyMoneyNotifier here, but rather clear the whole cache
+ d->m_cache.clear();
+ }
+
+ // process all accounts in the list and test if they have transactions assigned
+ for (QStringList::const_iterator it = account_list.begin(); it != account_list.end(); ++it) {
+ MyMoneyAccount a = m_storage->account(*it);
+ //kdDebug() << "Deleting account '"<< a.name() << "'" << endl;
+
+ // first remove all sub-accounts
+ if (!a.accountList().isEmpty())
+ removeAccountList(a.accountList(), level+1);
+
+ // then remove account itself, but we first have to get
+ // rid of the account list that is still stored in
+ // the MyMoneyAccount object. Easiest way is to get a fresh copy.
+ a = m_storage->account(*it);
+ //kdDebug() << "Deleting account '"<< a2.name() << "' itself" << endl;
+ m_storage->removeAccount(a);
+ }
+}
+
+bool MyMoneyFile::hasOnlyUnusedAccounts(const QStringList& account_list, unsigned int level)
+{
+ if (level > 100)
+ throw new MYMONEYEXCEPTION("Too deep recursion in [MyMoneyFile::hasOnlyUnusedAccounts]!");
+ // process all accounts in the list and test if they have transactions assigned
+ for (QStringList::const_iterator it = account_list.begin(); it != account_list.end(); ++it) {
+ if (transactionCount(*it) != 0)
+ return false; // the current account has a transaction assigned
+ if (!hasOnlyUnusedAccounts(account(*it).accountList(), level+1))
+ return false; // some sub-account has a transaction assigned
+ }
+ return true; // all subaccounts unused
+}
+
+
+void MyMoneyFile::removeInstitution(const MyMoneyInstitution& institution)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ QValueList<QString>::ConstIterator it_a;
+ MyMoneyInstitution inst = MyMoneyFile::institution(institution.id());
+
+ bool blocked = signalsBlocked();
+ blockSignals(true);
+ for(it_a = inst.accountList().begin(); it_a != inst.accountList().end(); ++it_a) {
+ MyMoneyAccount acc = account(*it_a);
+ acc.setInstitutionId(QString());
+ modifyAccount(acc);
+ }
+ blockSignals(blocked);
+
+ m_storage->removeInstitution(institution);
+
+ addNotification(institution.id(), false);
+}
+
+void MyMoneyFile::addAccount(MyMoneyAccount& account, MyMoneyAccount& parent)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ MyMoneyInstitution institution;
+
+ // perform some checks to see that the account stuff is OK. For
+ // now we assume that the account must have a name, has no
+ // transaction and sub-accounts and parent account
+ // it's own ID is not set and it does not have a pointer to (MyMoneyFile)
+
+ if(account.name().length() == 0)
+ throw new MYMONEYEXCEPTION("Account has no name");
+
+ if(account.id().length() != 0)
+ throw new MYMONEYEXCEPTION("New account must have no id");
+
+ if(account.accountList().count() != 0)
+ throw new MYMONEYEXCEPTION("New account must have no sub-accounts");
+
+ if(!account.parentAccountId().isEmpty())
+ throw new MYMONEYEXCEPTION("New account must have no parent-id");
+
+ if(account.accountType() == MyMoneyAccount::UnknownAccountType)
+ throw new MYMONEYEXCEPTION("Account has invalid type");
+
+ // make sure, that the parent account exists
+ // if not, an exception is thrown. If it exists,
+ // get a copy of the current data
+ MyMoneyAccount acc = MyMoneyFile::account(parent.id());
+
+#if 0
+ // TODO: remove the following code as we now can have multiple accounts
+ // with the same name even in the same hierarchy position of the account tree
+ //
+ // check if the selected name is currently not among the child accounts
+ // if we find one, then return it as the new account
+ QStringList::const_iterator it_a;
+ for(it_a = acc.accountList().begin(); it_a != acc.accountList().end(); ++it_a) {
+ MyMoneyAccount a = MyMoneyFile::account(*it_a);
+ if(account.name() == a.name()) {
+ account = a;
+ return;
+ }
+ }
+#endif
+
+ // FIXME: make sure, that the parent has the same type
+ // I left it out here because I don't know, if there is
+ // a tight coupling between e.g. checking accounts and the
+ // class asset. It certainly does not make sense to create an
+ // expense account under an income account. Maybe it does, I don't know.
+
+ // We enforce, that a stock account can never be a parent and
+ // that the parent for a stock account must be an investment. Also,
+ // an investment cannot have another investment account as it's parent
+ if(parent.isInvest())
+ throw new MYMONEYEXCEPTION("Stock account cannot be parent account");
+
+ if(account.isInvest() && parent.accountType() != MyMoneyAccount::Investment)
+ throw new MYMONEYEXCEPTION("Stock account must have investment account as parent ");
+
+ if(!account.isInvest() && parent.accountType() == MyMoneyAccount::Investment)
+ throw new MYMONEYEXCEPTION("Investment account can only have stock accounts as children");
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ // if an institution is set, verify that it exists
+ if(account.institutionId().length() != 0) {
+ // check the presence of the institution. if it
+ // does not exist, an exception is thrown
+ institution = MyMoneyFile::institution(account.institutionId());
+ }
+
+
+ if(!account.openingDate().isValid()) {
+ account.setOpeningDate(QDate::currentDate());
+ }
+
+ account.setParentAccountId(parent.id());
+
+ m_storage->addAccount(account);
+ m_storage->addAccount(parent, account);
+
+ if(account.institutionId().length() != 0) {
+ institution.addAccountId(account.id());
+ m_storage->modifyInstitution(institution);
+ addNotification(institution.id());
+ }
+
+ d->m_cache.preloadAccount(account);
+ addNotification(parent.id());
+}
+
+MyMoneyTransaction MyMoneyFile::createOpeningBalanceTransaction(const MyMoneyAccount& acc, const MyMoneyMoney& balance)
+{
+ MyMoneyTransaction t;
+ // if the opening balance is not zero, we need
+ // to create the respective transaction
+ if(!balance.isZero()) {
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ MyMoneySecurity currency = security(acc.currencyId());
+ MyMoneyAccount openAcc = openingBalanceAccount(currency);
+
+ if(openAcc.openingDate() > acc.openingDate()) {
+ openAcc.setOpeningDate(acc.openingDate());
+ modifyAccount(openAcc);
+ }
+
+ MyMoneySplit s;
+
+ t.setPostDate(acc.openingDate());
+ t.setCommodity(acc.currencyId());
+
+ s.setAccountId(acc.id());
+ s.setShares(balance);
+ s.setValue(balance);
+ t.addSplit(s);
+
+ s.clearId();
+ s.setAccountId(openAcc.id());
+ s.setShares(-balance);
+ s.setValue(-balance);
+ t.addSplit(s);
+
+ addTransaction(t);
+ }
+ return t;
+}
+
+QString MyMoneyFile::openingBalanceTransaction(const MyMoneyAccount& acc) const
+{
+ QString result;
+
+ MyMoneySecurity currency = security(acc.currencyId());
+ MyMoneyAccount openAcc;
+
+ try
+ {
+ openAcc = openingBalanceAccount(currency);
+ }
+ catch(MyMoneyException *e)
+ {
+ delete e;
+ return result;
+ }
+
+ // Iterate over all the opening balance transactions for this currency
+ MyMoneyTransactionFilter filter;
+ filter.addAccount(openAcc.id());
+ QValueList<MyMoneyTransaction> transactions = transactionList(filter);
+ QValueList<MyMoneyTransaction>::const_iterator it_t = transactions.begin();
+ while ( it_t != transactions.end() )
+ {
+ try
+ {
+ // Test whether the transaction also includes a split into
+ // this account
+ (*it_t).splitByAccount(acc.id(), true /*match*/);
+
+ // If so, we have a winner!
+ result = (*it_t).id();
+ break;
+ }
+ catch(MyMoneyException *e)
+ {
+ // If not, keep searching
+ ++it_t;
+ delete e;
+ }
+ }
+
+ return result;
+}
+
+const MyMoneyAccount MyMoneyFile::openingBalanceAccount(const MyMoneySecurity& security)
+{
+ if(!security.isCurrency())
+ throw new MYMONEYEXCEPTION("Opening balance for non currencies not supported");
+
+ try
+ {
+ return openingBalanceAccount_internal(security);
+ }
+ catch(MyMoneyException *e)
+ {
+ delete e;
+ MyMoneyFileTransaction ft;
+ MyMoneyAccount acc;
+
+ try {
+ acc = createOpeningBalanceAccount(security);
+ ft.commit();
+
+ } catch(MyMoneyException* e) {
+ qDebug("Unable to create opening balance account for security %s", security.id().data());
+ delete e;
+ }
+ return acc;
+ }
+}
+
+const MyMoneyAccount MyMoneyFile::openingBalanceAccount(const MyMoneySecurity& security) const
+{
+ return openingBalanceAccount_internal(security);
+}
+
+const MyMoneyAccount MyMoneyFile::openingBalanceAccount_internal(const MyMoneySecurity& security) const
+{
+ if(!security.isCurrency())
+ throw new MYMONEYEXCEPTION("Opening balance for non currencies not supported");
+
+ MyMoneyAccount acc;
+ QRegExp match(QString("^%1").arg(i18n(MyMoneyFile::OpeningBalancesPrefix)));
+
+ QValueList<MyMoneyAccount> accounts;
+ QValueList<MyMoneyAccount>::Iterator it;
+
+ accountList(accounts, equity().accountList(), true);
+
+ for(it = accounts.begin(); it != accounts.end(); ++it) {
+ if(match.search((*it).name()) != -1) {
+ if((*it).currencyId() == security.id()) {
+ acc = *it;
+ break;
+ }
+ }
+ }
+
+ if(acc.id().isEmpty()) {
+ throw new MYMONEYEXCEPTION(QString("No opening balance account for %1").arg(security.tradingSymbol()));
+ }
+
+ return acc;
+}
+
+const MyMoneyAccount MyMoneyFile::createOpeningBalanceAccount(const MyMoneySecurity& security)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ MyMoneyAccount acc;
+ QString name(i18n(MyMoneyFile::OpeningBalancesPrefix));
+ if(security.id() != baseCurrency().id()) {
+ name += QString(" (%1)").arg(security.id());
+ }
+ acc.setName(name);
+ acc.setAccountType(MyMoneyAccount::Equity);
+ acc.setCurrencyId(security.id());
+
+ MyMoneyAccount parent = equity();
+ this->addAccount(acc, parent);
+ return acc;
+}
+
+void MyMoneyFile::addTransaction(MyMoneyTransaction& transaction)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ // perform some checks to see that the transaction stuff is OK. For
+ // now we assume that
+ // * no ids are assigned
+ // * the date valid (must not be empty)
+ // * the referenced accounts in the splits exist
+
+ // first perform all the checks
+ if(!transaction.id().isEmpty())
+ throw new MYMONEYEXCEPTION("Unable to add transaction with id set");
+ if(!transaction.postDate().isValid())
+ throw new MYMONEYEXCEPTION("Unable to add transaction with invalid postdate");
+
+ // now check the splits
+ bool loanAccountAffected = false;
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+ for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
+ // the following line will throw an exception if the
+ // account does not exist or is one of the standard accounts
+ MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());
+ if(acc.id().isEmpty())
+ throw new MYMONEYEXCEPTION("Cannot add split with no account assigned");
+ if(acc.isLoan())
+ loanAccountAffected = true;
+ if(isStandardAccount((*it_s).accountId()))
+ throw new MYMONEYEXCEPTION("Cannot add split referencing standard account");
+ }
+
+ // change transfer splits between asset/liability and loan accounts
+ // into amortization splits
+ if(loanAccountAffected) {
+ QValueList<MyMoneySplit> list = transaction.splits();
+ for(it_s = list.begin(); it_s != list.end(); ++it_s) {
+ if((*it_s).action() == MyMoneySplit::ActionTransfer) {
+ MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());
+
+ if(acc.isAssetLiability()) {
+ MyMoneySplit s = (*it_s);
+ s.setAction(MyMoneySplit::ActionAmortization);
+ transaction.modifySplit(s);
+ }
+ }
+ }
+ }
+
+ // check that we have a commodity
+ if(transaction.commodity().isEmpty()) {
+ transaction.setCommodity(baseCurrency().id());
+ }
+
+ // then add the transaction to the file global pool
+ m_storage->addTransaction(transaction);
+
+ // scan the splits again to update notification list
+ for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
+ addNotification((*it_s).accountId());
+ addNotification((*it_s).payeeId());
+ }
+}
+
+const MyMoneyTransaction MyMoneyFile::transaction(const QString& id) const
+{
+ checkStorage();
+
+ return m_storage->transaction(id);
+}
+
+const MyMoneyTransaction MyMoneyFile::transaction(const QString& account, const int idx) const
+{
+ checkStorage();
+
+ return m_storage->transaction(account, idx);
+}
+
+void MyMoneyFile::addPayee(MyMoneyPayee& payee)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->addPayee(payee);
+
+ d->m_cache.preloadPayee(payee);
+}
+
+const MyMoneyPayee& MyMoneyFile::payee(const QString& id) const
+{
+ return d->m_cache.payee(id);
+}
+
+const MyMoneyPayee& MyMoneyFile::payeeByName(const QString& name) const
+{
+ checkStorage();
+
+ return d->m_cache.payee(m_storage->payeeByName(name).id());
+}
+
+void MyMoneyFile::modifyPayee(const MyMoneyPayee& payee)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ addNotification(payee.id());
+
+ m_storage->modifyPayee(payee);
+}
+
+void MyMoneyFile::removePayee(const MyMoneyPayee& payee)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->removePayee(payee);
+
+ addNotification(payee.id(), false);
+}
+
+void MyMoneyFile::accountList(QValueList<MyMoneyAccount>& list, const QStringList& idlist, const bool recursive) const
+{
+ if(idlist.isEmpty()) {
+ d->m_cache.account(list);
+
+#if 0
+ // TODO: I have no idea what this was good for, but it caused the networth report
+ // to show double the numbers so I commented it out (ipwizard, 2008-05-24)
+ if (m_storage && (list.isEmpty() || list.size() != m_storage->accountCount())) {
+ m_storage->accountList(list);
+ d->m_cache.preloadAccount(list);
+ }
+#endif
+
+ QValueList<MyMoneyAccount>::Iterator it;
+ QValueList<MyMoneyAccount>::Iterator next;
+ for(it = list.begin(); it != list.end(); ) {
+ if(isStandardAccount( (*it).id() )) {
+ it = list.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ } else {
+ QValueList<MyMoneyAccount>::ConstIterator it;
+ QValueList<MyMoneyAccount> list_a;
+ d->m_cache.account(list_a);
+
+ for(it = list_a.begin(); it != list_a.end(); ++it) {
+ if(!isStandardAccount((*it).id())) {
+ if(idlist.findIndex((*it).id()) != -1) {
+ list.append(*it);
+ if(recursive == true) {
+ accountList(list, (*it).accountList(), true);
+ }
+ }
+ }
+ }
+ }
+}
+
+void MyMoneyFile::institutionList(QValueList<MyMoneyInstitution>& list) const
+{
+ d->m_cache.institution(list);
+}
+
+const QValueList<MyMoneyInstitution> MyMoneyFile::institutionList(void) const
+{
+ QValueList<MyMoneyInstitution> list;
+ institutionList(list);
+ return list;
+}
+
+// general get functions
+const MyMoneyPayee MyMoneyFile::user(void) const { checkStorage(); return m_storage->user(); }
+
+// general set functions
+void MyMoneyFile::setUser(const MyMoneyPayee& user)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->setUser(user);
+}
+
+bool MyMoneyFile::dirty(void) const
+{
+ if(!m_storage)
+ return false;
+
+ return m_storage->dirty();
+}
+
+void MyMoneyFile::setDirty(void) const
+{
+ checkStorage();
+
+ m_storage->setDirty();
+}
+
+unsigned int MyMoneyFile::accountCount(void) const
+{
+ checkStorage();
+
+ return m_storage->accountCount();
+}
+
+void MyMoneyFile::ensureDefaultCurrency(MyMoneyAccount& acc) const
+{
+ if(acc.currencyId().isEmpty()) {
+ if(!baseCurrency().id().isEmpty())
+ acc.setCurrencyId(baseCurrency().id());
+ }
+}
+
+const MyMoneyAccount& MyMoneyFile::liability(void) const
+{
+ checkStorage();
+
+ return d->m_cache.account (STD_ACC_LIABILITY);
+}
+
+const MyMoneyAccount& MyMoneyFile::asset(void) const
+{
+ checkStorage();
+
+ return d->m_cache.account (STD_ACC_ASSET);
+}
+
+const MyMoneyAccount& MyMoneyFile::expense(void) const
+{
+ checkStorage();
+
+ return d->m_cache.account (STD_ACC_EXPENSE);
+}
+
+const MyMoneyAccount& MyMoneyFile::income(void) const
+{
+ checkStorage();
+
+ return d->m_cache.account (STD_ACC_INCOME);
+}
+
+const MyMoneyAccount& MyMoneyFile::equity(void) const
+{
+ checkStorage();
+
+ return d->m_cache.account (STD_ACC_EQUITY);
+}
+
+unsigned int MyMoneyFile::transactionCount(const QString& account) const
+{
+ checkStorage();
+
+ return m_storage->transactionCount(account);
+}
+
+const QMap<QString, unsigned long> MyMoneyFile::transactionCountMap(void) const
+{
+ checkStorage();
+
+ return m_storage->transactionCountMap();
+}
+
+unsigned int MyMoneyFile::institutionCount(void) const
+{
+ checkStorage();
+
+ return m_storage->institutionCount();
+}
+
+const MyMoneyMoney MyMoneyFile::balance(const QString& id, const QDate& date) const
+{
+ checkStorage();
+
+ return m_storage->balance(id, date);
+}
+
+const MyMoneyMoney MyMoneyFile::totalBalance(const QString& id, const QDate& date) const
+{
+ checkStorage();
+
+ return m_storage->totalBalance(id, date);
+}
+
+void MyMoneyFile::warningMissingRate(const QString& fromId, const QString& toId) const
+{
+ MyMoneySecurity from, to;
+ try {
+ from = security(fromId);
+ to = security(toId);
+ qWarning("Missing price info for conversion from %s to %s", from.name().latin1(), to.name().latin1());
+
+ } catch(MyMoneyException *e) {
+ qFatal("Missing security caught in MyMoneyFile::warningMissingRate(): %s(%ld) %s", e->file().data(), e->line(), e->what().data());
+ delete e;
+ }
+}
+
+void MyMoneyFile::notify(void)
+{
+ QMap<QString, bool>::ConstIterator it;
+ for(it = d->m_notificationList.begin(); it != d->m_notificationList.end(); ++it) {
+ if(*it)
+ d->m_cache.refresh(it.key());
+ else
+ d->m_cache.clear(it.key());
+ }
+ clearNotification();
+}
+
+void MyMoneyFile::addNotification(const QString& id, bool reload)
+{
+ if(!id.isEmpty())
+ d->m_notificationList[id] = reload;
+}
+
+void MyMoneyFile::clearNotification()
+{
+ // reset list to be empty
+ d->m_notificationList.clear();
+}
+
+void MyMoneyFile::transactionList(QValueList<QPair<MyMoneyTransaction, MyMoneySplit> >& list, MyMoneyTransactionFilter& filter) const
+{
+ checkStorage();
+ m_storage->transactionList(list, filter);
+}
+
+void MyMoneyFile::transactionList(QValueList<MyMoneyTransaction>& list, MyMoneyTransactionFilter& filter) const
+{
+ checkStorage();
+ m_storage->transactionList(list, filter);
+}
+
+const QValueList<MyMoneyTransaction> MyMoneyFile::transactionList(MyMoneyTransactionFilter& filter) const
+{
+ QValueList<MyMoneyTransaction> list;
+ transactionList(list, filter);
+ return list;
+}
+
+const QValueList<MyMoneyPayee> MyMoneyFile::payeeList(void) const
+{
+ QValueList<MyMoneyPayee> list;
+ d->m_cache.payee(list);
+ return list;
+}
+
+QString MyMoneyFile::accountToCategory(const QString& accountId, bool includeStandardAccounts) const
+{
+ MyMoneyAccount acc;
+ QString rc;
+
+ if(!accountId.isEmpty()) {
+ acc = account(accountId);
+ do {
+ if(!rc.isEmpty())
+ rc = AccountSeperator + rc;
+ rc = acc.name() + rc;
+ acc = account(acc.parentAccountId());
+ } while(!acc.id().isEmpty() && (includeStandardAccounts || !isStandardAccount(acc.id())));
+ }
+ return rc;
+}
+
+QString MyMoneyFile::categoryToAccount(const QString& category, MyMoneyAccount::accountTypeE type) const
+{
+ QString id;
+
+ // search the category in the expense accounts and if it is not found, try
+ // to locate it in the income accounts
+ if(type == MyMoneyAccount::UnknownAccountType
+ || type == MyMoneyAccount::Expense) {
+ id = locateSubAccount(MyMoneyFile::instance()->expense(), category);
+ }
+
+ if((id.isEmpty() && type == MyMoneyAccount::UnknownAccountType)
+ || type == MyMoneyAccount::Income) {
+ id = locateSubAccount(MyMoneyFile::instance()->income(), category);
+ }
+
+ return id;
+}
+
+QString MyMoneyFile::nameToAccount(const QString& name) const
+{
+ QString id;
+
+ // search the category in the asset accounts and if it is not found, try
+ // to locate it in the liability accounts
+ id = locateSubAccount(MyMoneyFile::instance()->asset(), name);
+ if(id.isEmpty())
+ id = locateSubAccount(MyMoneyFile::instance()->liability(), name);
+
+ return id;
+}
+
+QString MyMoneyFile::parentName(const QString& name) const
+{
+ return name.section(AccountSeperator, 0, -2);
+}
+
+QString MyMoneyFile::locateSubAccount(const MyMoneyAccount& base, const QString& category) const
+{
+ MyMoneyAccount nextBase;
+ QString level, remainder;
+ level = category.section(AccountSeperator, 0, 0);
+ remainder = category.section(AccountSeperator, 1);
+
+ QStringList list = base.accountList();
+ QStringList::ConstIterator it_a;
+
+ for(it_a = list.begin(); it_a != list.end(); ++it_a) {
+ nextBase = account(*it_a);
+ if(nextBase.name() == level) {
+ if(remainder.isEmpty()) {
+ return nextBase.id();
+ }
+ return locateSubAccount(nextBase, remainder);
+ }
+ }
+ return QString();
+}
+
+QString MyMoneyFile::value(const QString& key) const
+{
+ checkStorage();
+
+ return m_storage->value(key);
+}
+
+void MyMoneyFile::setValue(const QString& key, const QString& val)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->setValue(key, val);
+}
+
+void MyMoneyFile::deletePair(const QString& key)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->deletePair(key);
+}
+
+void MyMoneyFile::addSchedule(MyMoneySchedule& sched)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ MyMoneyTransaction transaction = sched.transaction();
+ QValueList<MyMoneySplit>::const_iterator it_s;
+ for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
+ // the following line will throw an exception if the
+ // account does not exist or is one of the standard accounts
+ MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());
+ if(acc.id().isEmpty())
+ throw new MYMONEYEXCEPTION("Cannot add split with no account assigned");
+ if(isStandardAccount((*it_s).accountId()))
+ throw new MYMONEYEXCEPTION("Cannot add split referencing standard account");
+ }
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->addSchedule(sched);
+}
+
+void MyMoneyFile::modifySchedule(const MyMoneySchedule& sched)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ MyMoneyTransaction transaction = sched.transaction();
+ QValueList<MyMoneySplit>::const_iterator it_s;
+ for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
+ // the following line will throw an exception if the
+ // account does not exist or is one of the standard accounts
+ MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());
+ if(acc.id().isEmpty())
+ throw new MYMONEYEXCEPTION("Cannot store split with no account assigned");
+ if(isStandardAccount((*it_s).accountId()))
+ throw new MYMONEYEXCEPTION("Cannot store split referencing standard account");
+ }
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->modifySchedule(sched);
+
+ addNotification(sched.id());
+}
+
+void MyMoneyFile::removeSchedule(const MyMoneySchedule& sched)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->removeSchedule(sched);
+
+ addNotification(sched.id(), false);
+}
+
+const MyMoneySchedule MyMoneyFile::schedule(const QString& id) const
+{
+ return d->m_cache.schedule(id);
+}
+
+const QValueList<MyMoneySchedule> MyMoneyFile::scheduleList(
+ const QString& accountId,
+ const MyMoneySchedule::typeE type,
+ const MyMoneySchedule::occurenceE occurence,
+ const MyMoneySchedule::paymentTypeE paymentType,
+ const QDate& startDate,
+ const QDate& endDate,
+ const bool overdue) const
+{
+ checkStorage();
+
+ return m_storage->scheduleList(accountId, type, occurence, paymentType, startDate, endDate, overdue);
+}
+
+
+const QStringList MyMoneyFile::consistencyCheck(void)
+{
+ QValueList<MyMoneyAccount> list;
+ QValueList<MyMoneyAccount>::Iterator it_a;
+ QValueList<MyMoneySchedule>::Iterator it_sch;
+ QValueList<MyMoneyPayee>::Iterator it_p;
+ QValueList<MyMoneyTransaction>::Iterator it_t;
+ QValueList<MyMoneyReport>::Iterator it_r;
+ QStringList accountRebuild;
+ QStringList::ConstIterator it_c;
+
+ QMap<QString, bool> interestAccounts;
+
+ MyMoneyAccount parent;
+ MyMoneyAccount child;
+ MyMoneyAccount toplevel;
+
+ QString parentId;
+ QStringList rc;
+
+ int problemCount = 0;
+ int unfixedCount = 0;
+ QString problemAccount;
+
+ // check that we have a storage object
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ // get the current list of accounts
+ accountList(list);
+ // add the standard accounts
+ list << MyMoneyFile::instance()->asset();
+ list << MyMoneyFile::instance()->liability();
+ list << MyMoneyFile::instance()->income();
+ list << MyMoneyFile::instance()->expense();
+
+ for(it_a = list.begin(); it_a != list.end(); ++it_a) {
+ // no more checks for standard accounts
+ if(isStandardAccount((*it_a).id())) {
+ continue;
+ }
+
+ switch((*it_a).accountGroup()) {
+ case MyMoneyAccount::Asset:
+ toplevel = asset();
+ break;
+ case MyMoneyAccount::Liability:
+ toplevel = liability();
+ break;
+ case MyMoneyAccount::Expense:
+ toplevel = expense();
+ break;
+ case MyMoneyAccount::Income:
+ toplevel = income();
+ break;
+ case MyMoneyAccount::Equity:
+ toplevel = equity();
+ break;
+ default:
+ qWarning("%s:%d This should never happen!", __FILE__ , __LINE__);
+ break;
+ }
+
+ // check for loops in the hierarchy
+ parentId = (*it_a).parentAccountId();
+ try {
+ bool dropOut = false;
+ while(!isStandardAccount(parentId) && !dropOut) {
+ parent = account(parentId);
+ if(parent.id() == (*it_a).id()) {
+ // parent loops, so we need to re-parent to toplevel account
+ // find parent account in our list
+ problemCount++;
+ QValueList<MyMoneyAccount>::Iterator it_b;
+ for(it_b = list.begin(); it_b != list.end(); ++it_b) {
+ if((*it_b).id() == parent.id()) {
+ if(problemAccount != (*it_a).name()) {
+ problemAccount = (*it_a).name();
+ rc << i18n("* Problem with account '%1'").arg(problemAccount);
+ rc << i18n(" * Loop detected between this account and account '%2'.").arg((*it_b).name());
+ rc << i18n(" Reparenting account '%2' to top level account '%1'.").arg(toplevel.name(), (*it_a).name());
+ (*it_a).setParentAccountId(toplevel.id());
+ if(accountRebuild.contains(toplevel.id()) == 0)
+ accountRebuild << toplevel.id();
+ if(accountRebuild.contains((*it_a).id()) == 0)
+ accountRebuild << (*it_a).id();
+ dropOut = true;
+ break;
+ }
+ }
+ }
+ }
+ parentId = parent.parentAccountId();
+ }
+
+ } catch(MyMoneyException *e) {
+ // if we don't know about a parent, we catch it later
+ delete e;
+ }
+
+ // check that the parent exists
+ parentId = (*it_a).parentAccountId();
+ try {
+ parent = account(parentId);
+ if((*it_a).accountGroup() != parent.accountGroup()) {
+ problemCount++;
+ if(problemAccount != (*it_a).name()) {
+ problemAccount = (*it_a).name();
+ rc << i18n("* Problem with account '%1'").arg(problemAccount);
+ }
+ // the parent belongs to a different group, so we reconnect to the
+ // master group account (asset, liability, etc) to which this account
+ // should belong and update it in the engine.
+ rc << i18n(" * Parent account '%1' belongs to a different group.").arg(parent.name());
+ rc << i18n(" New parent account is the top level account '%1'.").arg(toplevel.name());
+ (*it_a).setParentAccountId(toplevel.id());
+
+ // make sure to rebuild the sub-accounts of the top account
+ // and the one we removed this account from
+ if(accountRebuild.contains(toplevel.id()) == 0)
+ accountRebuild << toplevel.id();
+ if(accountRebuild.contains(parent.id()) == 0)
+ accountRebuild << parent.id();
+ } else if(!parent.accountList().contains((*it_a).id())) {
+ problemCount++;
+ if(problemAccount != (*it_a).name()) {
+ problemAccount = (*it_a).name();
+ rc << i18n("* Problem with account '%1'").arg(problemAccount);
+ }
+ // parent exists, but does not have a reference to the account
+ rc << i18n(" * Parent account '%1' does not contain '%2' as sub-account.").arg(parent.name(), problemAccount);
+ if(accountRebuild.contains(parent.id()) == 0)
+ accountRebuild << parent.id();
+ }
+ } catch(MyMoneyException *e) {
+ delete e;
+ // apparently, the parent does not exist anymore. we reconnect to the
+ // master group account (asset, liability, etc) to which this account
+ // should belong and update it in the engine.
+ problemCount++;
+ if(problemAccount != (*it_a).name()) {
+ problemAccount = (*it_a).name();
+ rc << i18n("* Problem with account '%1'").arg(problemAccount);
+ }
+ rc << i18n(" * The parent with id %1 does not exist anymore.").arg(parentId);
+ rc << i18n(" New parent account is the top level account '%1'.").arg(toplevel.name());
+ (*it_a).setParentAccountId(toplevel.id());
+
+ addNotification((*it_a).id());
+
+ // make sure to rebuild the sub-accounts of the top account
+ if(accountRebuild.contains(toplevel.id()) == 0)
+ accountRebuild << toplevel.id();
+ }
+
+ // now check that all the children exist and have the correct type
+ for(it_c = (*it_a).accountList().begin(); it_c != (*it_a).accountList().end(); ++it_c) {
+ // check that the child exists
+ try {
+ child = account(*it_c);
+ } catch(MyMoneyException *e) {
+ problemCount++;
+ if(problemAccount != (*it_a).name()) {
+ problemAccount = (*it_a).name();
+ rc << i18n("* Problem with account '%1'").arg(problemAccount);
+ }
+ rc << i18n(" * Child account with id %1 does not exist anymore.").arg(*it_c);
+ rc << i18n(" The child account list will be reconstructed.");
+ if(accountRebuild.contains((*it_a).id()) == 0)
+ accountRebuild << (*it_a).id();
+ }
+ }
+
+ // see if it is a loan account. if so, remember the assigned interest account
+ if((*it_a).isLoan()) {
+ const MyMoneyAccountLoan* loan = dynamic_cast<MyMoneyAccountLoan*>(&(*it_a));
+ if(!loan->interestAccountId().isEmpty())
+ interestAccounts[loan->interestAccountId()] = true;
+ }
+
+ // if the account was modified, we need to update it in the engine
+ if(!(m_storage->account((*it_a).id()) == (*it_a))) {
+ try {
+ m_storage->modifyAccount(*it_a, true);
+ addNotification((*it_a).id());
+ } catch (MyMoneyException *e) {
+ delete e;
+ rc << i18n(" * Unable to update account data in engine.");
+ return rc;
+ }
+ }
+ }
+
+ if(accountRebuild.count() != 0) {
+ rc << i18n("* Reconstructing the child lists for");
+ }
+
+ // clear the affected lists
+ for(it_a = list.begin(); it_a != list.end(); ++it_a) {
+ if(accountRebuild.contains((*it_a).id())) {
+ rc << QString(" %1").arg((*it_a).name());
+ // clear the account list
+ for(it_c = (*it_a).accountList().begin(); it_c != (*it_a).accountList().end();) {
+ (*it_a).removeAccountId(*it_c);
+ it_c = (*it_a).accountList().begin();
+ }
+ }
+ }
+
+ // reconstruct the lists
+ for(it_a = list.begin(); it_a != list.end(); ++it_a) {
+ QValueList<MyMoneyAccount>::Iterator it;
+ parentId = (*it_a).parentAccountId();
+ if(accountRebuild.contains(parentId)) {
+ for(it = list.begin(); it != list.end(); ++it) {
+ if((*it).id() == parentId) {
+ (*it).addAccountId((*it_a).id());
+ break;
+ }
+ }
+ }
+ }
+
+ // update the engine objects
+ for(it_a = list.begin(); it_a != list.end(); ++it_a) {
+ if(accountRebuild.contains((*it_a).id())) {
+ try {
+ m_storage->modifyAccount(*it_a, true);
+ addNotification((*it_a).id());
+ } catch (MyMoneyException *e) {
+ delete e;
+ rc << i18n(" * Unable to update account data for account %1 in engine").arg((*it_a).name());
+ }
+ }
+ }
+
+ // For some reason, files exist with invalid ids. This has been found in the payee id
+ // so we fix them here
+ QValueList<MyMoneyPayee> pList = payeeList();
+ QMap<QString, QString>payeeConversionMap;
+
+ for(it_p = pList.begin(); it_p != pList.end(); ++it_p) {
+ if((*it_p).id().length() > 7) {
+ // found one of those with an invalid ids
+ // create a new one and store it in the map.
+ MyMoneyPayee payee = (*it_p);
+ payee.clearId();
+ m_storage->addPayee(payee);
+ payeeConversionMap[(*it_p).id()] = payee.id();
+ rc << i18n(" * Payee %1 recreated with fixed id").arg(payee.name());
+ ++problemCount;
+ }
+ }
+
+ // Fix the transactions
+ QValueList<MyMoneyTransaction> tList;
+ MyMoneyTransactionFilter filter;
+ filter.setReportAllSplits(false);
+ m_storage->transactionList(tList, filter);
+ // Generate the list of interest accounts
+ for(it_t = tList.begin(); it_t != tList.end(); ++it_t) {
+ const MyMoneyTransaction& t = (*it_t);
+ QValueList<MyMoneySplit>::const_iterator it_s;
+ for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
+ if((*it_s).action() == MyMoneySplit::ActionInterest)
+ interestAccounts[(*it_s).accountId()] = true;
+ }
+ }
+ for(it_t = tList.begin(); it_t != tList.end(); ++it_t) {
+ MyMoneyTransaction t = (*it_t);
+ QValueList<MyMoneySplit>::const_iterator it_s;
+ bool tChanged = false;
+ for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
+ bool sChanged = false;
+ MyMoneySplit s = (*it_s);
+ if(payeeConversionMap.find((*it_s).payeeId()) != payeeConversionMap.end()) {
+ s.setPayeeId(payeeConversionMap[s.payeeId()]);
+ sChanged = true;
+ rc << i18n(" * Payee id updated in split of transaction '%1'.").arg(t.id());
+ ++problemCount;
+ }
+
+ // make sure, that shares and value have the same number if they
+ // represent the same currency.
+ try {
+ const MyMoneyAccount& acc = this->account(s.accountId());
+ if(t.commodity() == acc.currencyId()
+ && s.shares().reduce() != s.value().reduce()) {
+ // use the value as master if the transaction is balanced
+ if(t.splitSum().isZero()) {
+ s.setShares(s.value());
+ rc << i18n(" * shares set to value in split of transaction '%1'.").arg(t.id());
+ } else {
+ s.setValue(s.shares());
+ rc << i18n(" * value set to shares in split of transaction '%1'.").arg(t.id());
+ }
+ sChanged = true;
+ ++problemCount;
+ }
+ } catch(MyMoneyException *e) {
+ rc << i18n(" * Split %2 in transaction '%1' contains a reference to invalid account %3. Please fix manually.").arg(t.id(), (*it_s).id(), (*it_s).accountId());
+ ++problemCount;
+ ++unfixedCount;
+ delete e;
+ }
+
+ // make sure the interest splits are marked correct as such
+ if(interestAccounts.find(s.accountId()) != interestAccounts.end()
+ && s.action() != MyMoneySplit::ActionInterest) {
+ s.setAction(MyMoneySplit::ActionInterest);
+ sChanged = true;
+ rc << i18n(" * action marked as interest in split of transaction '%1'.").arg(t.id());
+ ++problemCount;
+ }
+
+ if(sChanged) {
+ tChanged = true;
+ t.modifySplit(s);
+ }
+ }
+ if(tChanged) {
+ m_storage->modifyTransaction(t);
+ }
+ }
+
+ // Fix the schedules
+ QValueList<MyMoneySchedule> schList = scheduleList();
+ for(it_sch = schList.begin(); it_sch != schList.end(); ++it_sch) {
+ MyMoneySchedule sch = (*it_sch);
+ MyMoneyTransaction t = sch.transaction();
+ bool tChanged = false;
+ QValueList<MyMoneySplit>::const_iterator it_s;
+ for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
+ MyMoneySplit s = (*it_s);
+ bool sChanged = false;
+ if(payeeConversionMap.find((*it_s).payeeId()) != payeeConversionMap.end()) {
+ s.setPayeeId(payeeConversionMap[s.payeeId()]);
+ sChanged = true;
+ rc << i18n(" * Payee id updated in split of schedule '%1'.").arg((*it_sch).name());
+ ++problemCount;
+ }
+ if(!(*it_s).value().isZero() && (*it_s).shares().isZero()) {
+ s.setShares(s.value());
+ sChanged = true;
+ rc << i18n(" * Split in scheduled transaction '%1' contained value != 0 and shares == 0.").arg((*it_sch).name());
+ rc << i18n(" Shares set to value.");
+ ++problemCount;
+ }
+
+ // make sure, we don't have a bankid stored with a split in a schedule
+ if(!(*it_s).bankID().isEmpty()) {
+ s.setBankID(QString());
+ sChanged = true;
+ rc << i18n(" * Removed bankid from split in scheduled transaction '%1'.").arg((*it_sch).name());
+ ++problemCount;
+ }
+
+ // make sure, that shares and value have the same number if they
+ // represent the same currency.
+ try {
+ const MyMoneyAccount& acc = this->account(s.accountId());
+ if(t.commodity() == acc.currencyId()
+ && s.shares().reduce() != s.value().reduce()) {
+ // use the value as master if the transaction is balanced
+ if(t.splitSum().isZero()) {
+ s.setShares(s.value());
+ rc << i18n(" * shares set to value in split in schedule '%1'.").arg((*it_sch).name());
+ } else {
+ s.setValue(s.shares());
+ rc << i18n(" * value set to shares in split in schedule '%1'.").arg((*it_sch).name());
+ }
+ sChanged = true;
+ ++problemCount;
+ }
+ } catch(MyMoneyException *e) {
+ rc << i18n(" * Split %2 in schedule '%1' contains a reference to invalid account %3. Please fix manually.").arg((*it_sch).name(), (*it_s).id(), (*it_s).accountId());
+ ++problemCount;
+ ++unfixedCount;
+ delete e;
+ }
+ if(sChanged) {
+ t.modifySplit(s);
+ tChanged = true;
+ }
+ }
+ if(tChanged) {
+ sch.setTransaction(t);
+ m_storage->modifySchedule(sch);
+ }
+ }
+
+ // Fix the reports
+ QValueList<MyMoneyReport> rList = reportList();
+ for(it_r = rList.begin(); it_r != rList.end(); ++it_r) {
+ MyMoneyReport r = *it_r;
+ QStringList pList;
+ QStringList::Iterator it_p;
+ (*it_r).payees(pList);
+ bool rChanged = false;
+ for(it_p = pList.begin(); it_p != pList.end(); ++it_p) {
+ if(payeeConversionMap.find(*it_p) != payeeConversionMap.end()) {
+ rc << i18n(" * Payee id updated in report '%1'.").arg((*it_r).name());
+ ++problemCount;
+ r.removeReference(*it_p);
+ r.addPayee(payeeConversionMap[*it_p]);
+ rChanged = true;
+ }
+ }
+ if(rChanged) {
+ m_storage->modifyReport(r);
+ }
+ }
+
+ // erase old payee ids
+ QMap<QString, QString>::Iterator it_m;
+ for(it_m = payeeConversionMap.begin(); it_m != payeeConversionMap.end(); ++it_m) {
+ MyMoneyPayee payee = this->payee(it_m.key());
+ removePayee(payee);
+ rc << i18n(" * Payee '%1' removed.").arg(payee.id());
+ ++problemCount;
+ }
+
+ // add more checks here
+
+ if(problemCount == 0)
+ rc << i18n("Finish! Data is consistent.");
+ else
+ rc << i18n("Finish! %1 problem(s) corrected. %2 problem(s) still present.")
+ .arg(problemCount).arg(unfixedCount);
+
+ return rc;
+}
+
+QString MyMoneyFile::createCategory(const MyMoneyAccount& base, const QString& name)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ MyMoneyAccount parent = base;
+ QString categoryText;
+
+ if(base.id() != expense().id() && base.id() != income().id())
+ throw new MYMONEYEXCEPTION("Invalid base category");
+
+ QStringList subAccounts = QStringList::split(AccountSeperator, name);
+ QStringList::Iterator it;
+ for (it = subAccounts.begin(); it != subAccounts.end(); ++it)
+ {
+ MyMoneyAccount categoryAccount;
+
+ categoryAccount.setName(*it);
+ categoryAccount.setAccountType(base.accountType());
+
+ if (it == subAccounts.begin())
+ categoryText += *it;
+ else
+ categoryText += (AccountSeperator + *it);
+
+ // Only create the account if it doesn't exist
+ try
+ {
+ QString categoryId = categoryToAccount(categoryText);
+ if (categoryId.isEmpty())
+ addAccount(categoryAccount, parent);
+ else
+ {
+ categoryAccount = account(categoryId);
+ }
+ }
+ catch (MyMoneyException *e)
+ {
+ qDebug("Unable to add account %s, %s, %s: %s",
+ categoryAccount.name().latin1(),
+ parent.name().latin1(),
+ categoryText.latin1(),
+ e->what().latin1());
+ delete e;
+ }
+
+ parent = categoryAccount;
+ }
+
+ return categoryToAccount(name);
+}
+
+const QValueList<MyMoneySchedule> MyMoneyFile::scheduleListEx( int scheduleTypes,
+ int scheduleOcurrences,
+ int schedulePaymentTypes,
+ QDate startDate,
+ const QStringList& accounts) const
+{
+ checkStorage();
+
+ return m_storage->scheduleListEx(scheduleTypes, scheduleOcurrences, schedulePaymentTypes, startDate, accounts);
+}
+
+void MyMoneyFile::addSecurity(MyMoneySecurity& security)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->addSecurity(security);
+
+ d->m_cache.preloadSecurity(security);
+}
+
+void MyMoneyFile::modifySecurity(const MyMoneySecurity& security)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->modifySecurity(security);
+
+ addNotification(security.id());
+}
+
+void MyMoneyFile::removeSecurity(const MyMoneySecurity& security)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->removeSecurity(security);
+
+ addNotification(security.id(), false);
+}
+
+const MyMoneySecurity& MyMoneyFile::security(const QString& id) const
+{
+ if(id.isEmpty())
+ return baseCurrency();
+
+ return d->m_cache.security(id);
+}
+
+const QValueList<MyMoneySecurity> MyMoneyFile::securityList(void) const
+{
+ checkStorage();
+
+ return m_storage->securityList();
+}
+
+void MyMoneyFile::addCurrency(const MyMoneySecurity& currency)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->addCurrency(currency);
+
+ // we can't really use addNotification here, because there is
+ // a difference in currency and security handling. So we just
+ // preload the object right here.
+ d->m_cache.preloadSecurity(currency);
+}
+
+void MyMoneyFile::modifyCurrency(const MyMoneySecurity& currency)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ // force reload of base currency object
+ if(currency.id() == d->m_baseCurrency.id())
+ d->m_baseCurrency.clearId();
+
+ m_storage->modifyCurrency(currency);
+
+ addNotification(currency.id());
+}
+
+void MyMoneyFile::removeCurrency(const MyMoneySecurity& currency)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ if(currency.id() == d->m_baseCurrency.id()) {
+ throw new MYMONEYEXCEPTION("Cannot delete base currency.");
+ }
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->removeCurrency(currency);
+
+ addNotification(currency.id(), false);
+}
+
+const MyMoneySecurity& MyMoneyFile::currency(const QString& id) const
+{
+ if(id.isEmpty())
+ return baseCurrency();
+
+ const MyMoneySecurity& curr = d->m_cache.security(id);
+ if(curr.id().isEmpty())
+ throw new MYMONEYEXCEPTION("Currency not found.");
+ return curr;
+}
+
+const QValueList<MyMoneySecurity> MyMoneyFile::currencyList(void) const
+{
+ checkStorage();
+
+ return m_storage->currencyList();
+}
+
+const QString& MyMoneyFile::foreignCurrency(const QString& first, const QString& second) const
+{
+ if(baseCurrency().id() == second)
+ return first;
+ return second;
+}
+
+const MyMoneySecurity& MyMoneyFile::baseCurrency(void) const
+{
+ if(d->m_baseCurrency.id().isEmpty()) {
+ QString id = QString(value("kmm-baseCurrency"));
+ if(!id.isEmpty())
+ d->m_baseCurrency = currency(id);
+ }
+
+ return d->m_baseCurrency;
+}
+
+void MyMoneyFile::setBaseCurrency(const MyMoneySecurity& curr)
+{
+ // make sure the currency exists
+ MyMoneySecurity c = currency(curr.id());
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ if(c.id() != d->m_baseCurrency.id()) {
+ setValue("kmm-baseCurrency", curr.id());
+ // force reload of base currency cache
+ d->m_baseCurrency = MyMoneySecurity();
+ }
+}
+
+void MyMoneyFile::addPrice(const MyMoneyPrice& price)
+{
+ if(price.rate(QString()).isZero())
+ return;
+
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->addPrice(price);
+}
+
+void MyMoneyFile::removePrice(const MyMoneyPrice& price)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->removePrice(price);
+}
+
+const MyMoneyPrice MyMoneyFile::price(const QString& fromId, const QString& toId, const QDate& date, const bool exactDate) const
+{
+ checkStorage();
+
+ QString to(toId);
+ if(to.isEmpty())
+ to = value("kmm-baseCurrency");
+ // if some id is missing, we can return an empty price object
+ if(fromId.isEmpty() || to.isEmpty())
+ return MyMoneyPrice();
+
+ // we don't search our tables if someone asks stupid stuff
+ if(fromId == toId) {
+ return MyMoneyPrice(fromId, toId, date, MyMoneyMoney(1,1), "KMyMoney");
+ }
+
+ // search 'from-to' rate
+ MyMoneyPrice rc = m_storage->price(fromId, to, date, exactDate);
+ if(!rc.isValid()) {
+ // not found, search 'to-fron' rate and use reciprocal value
+ rc = m_storage->price(to, fromId, date, exactDate);
+ }
+ return rc;
+}
+
+const MyMoneyPriceList MyMoneyFile::priceList(void) const
+{
+ checkStorage();
+
+ return m_storage->priceList();
+}
+
+bool MyMoneyFile::hasAccount(const QString& id, const QString& name) const
+{
+ MyMoneyAccount acc = d->m_cache.account(id);
+ QStringList list = acc.accountList();
+ QStringList::ConstIterator it;
+ bool rc = false;
+ for(it = list.begin(); rc == false && it != list.end(); ++it) {
+ MyMoneyAccount a = d->m_cache.account(*it);
+ if(a.name() == name)
+ rc = true;
+ }
+ return rc;
+}
+
+const QValueList<MyMoneyReport> MyMoneyFile::reportList( void ) const
+{
+ checkStorage();
+
+ return m_storage->reportList();
+}
+
+void MyMoneyFile::addReport( MyMoneyReport& report )
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->addReport( report );
+}
+
+void MyMoneyFile::modifyReport( const MyMoneyReport& report )
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->modifyReport( report );
+
+ addNotification(report.id());
+}
+
+unsigned MyMoneyFile::countReports(void) const
+{
+ checkStorage();
+
+ return m_storage->countReports();
+}
+
+const MyMoneyReport MyMoneyFile::report( const QString& id ) const
+{
+ checkStorage();
+
+ return m_storage->report(id);
+}
+
+void MyMoneyFile::removeReport(const MyMoneyReport& report)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+ MyMoneyNotifier notifier(this);
+
+ m_storage->removeReport(report);
+
+ addNotification(report.id(), false);
+}
+
+
+const QValueList<MyMoneyBudget> MyMoneyFile::budgetList( void ) const
+{
+ checkStorage();
+
+ return m_storage->budgetList();
+}
+
+void MyMoneyFile::addBudget( MyMoneyBudget& budget )
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->addBudget( budget );
+}
+
+const MyMoneyBudget MyMoneyFile::budgetByName(const QString& name) const
+{
+ checkStorage();
+
+ return m_storage->budgetByName(name);
+}
+
+void MyMoneyFile::modifyBudget( const MyMoneyBudget& budget )
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->modifyBudget( budget );
+
+ addNotification(budget.id());
+}
+
+unsigned MyMoneyFile::countBudgets(void) const
+{
+ checkStorage();
+
+ return m_storage->countBudgets();
+}
+
+const MyMoneyBudget MyMoneyFile::budget( const QString& id ) const
+{
+ checkStorage();
+
+ return m_storage->budget(id);
+}
+
+void MyMoneyFile::removeBudget(const MyMoneyBudget& budget)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+ MyMoneyNotifier notifier(this);
+
+ m_storage->removeBudget(budget);
+
+ addNotification(budget.id(), false);
+}
+
+
+
+bool MyMoneyFile::isReferenced(const MyMoneyObject& obj, const MyMoneyFileBitArray& skipChecks) const
+{
+ checkStorage();
+ return m_storage->isReferenced(obj, skipChecks);
+}
+
+bool MyMoneyFile::checkNoUsed(const QString& accId, const QString& no) const
+{
+ // by definition, an empty string or a non-numeric string is not used
+ QRegExp exp(QString("(.*\\D)?(\\d+)(\\D.*)?"));
+ if(no.isEmpty() || exp.search(no) == -1)
+ return false;
+
+ MyMoneyTransactionFilter filter;
+ filter.addAccount(accId);
+ QValueList<MyMoneyTransaction> transactions = transactionList(filter);
+ QValueList<MyMoneyTransaction>::const_iterator it_t = transactions.begin();
+ while ( it_t != transactions.end() ) {
+ try {
+ MyMoneySplit split;
+ // Test whether the transaction also includes a split into
+ // this account
+ split = (*it_t).splitByAccount(accId, true /*match*/);
+ if(!split.number().isEmpty() && split.number() == no)
+ return true;
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+ ++it_t;
+ }
+ return false;
+}
+
+QString MyMoneyFile::highestCheckNo(const QString& accId) const
+{
+ unsigned64 lno = 0, cno;
+ QString no;
+ MyMoneyTransactionFilter filter;
+ filter.addAccount(accId);
+ QValueList<MyMoneyTransaction> transactions = transactionList(filter);
+ QValueList<MyMoneyTransaction>::const_iterator it_t = transactions.begin();
+ while ( it_t != transactions.end() ) {
+ try {
+ // Test whether the transaction also includes a split into
+ // this account
+ MyMoneySplit split = (*it_t).splitByAccount(accId, true /*match*/);
+ if(!split.number().isEmpty()) {
+ // non-numerical values stored in number will return 0 in the next line
+ cno = split.number().toULongLong();
+ if(cno > lno) {
+ lno = cno;
+ no = split.number();
+ }
+ }
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+ ++it_t;
+ }
+ return no;
+}
+
+void MyMoneyFile::clearCache(void)
+{
+ checkStorage();
+ m_storage->clearCache();
+ d->m_cache.clear();
+}
+
+void MyMoneyFile::preloadCache(void)
+{
+ checkStorage();
+
+ d->m_cache.clear();
+ QValueList<MyMoneyAccount> a_list;
+ m_storage->accountList(a_list);
+ d->m_cache.preloadAccount(a_list);
+ d->m_cache.preloadPayee(m_storage->payeeList());
+ d->m_cache.preloadInstitution(m_storage->institutionList());
+ d->m_cache.preloadSecurity(m_storage->securityList() + m_storage->currencyList());
+ d->m_cache.preloadSchedule(m_storage->scheduleList());
+}
+
+bool MyMoneyFile::isTransfer(const MyMoneyTransaction& t) const
+{
+ bool rc = false;
+ if(t.splitCount() == 2) {
+ QValueList<MyMoneySplit>::const_iterator it_s;
+ for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
+ MyMoneyAccount acc = account((*it_s).accountId());
+ if(acc.isIncomeExpense())
+ break;
+ }
+ if(it_s == t.splits().end())
+ rc = true;
+ }
+ return rc;
+}
+
+bool MyMoneyFile::referencesClosedAccount(const MyMoneyTransaction& t) const
+{
+ QValueList<MyMoneySplit>::const_iterator it_s;
+ const QValueList<MyMoneySplit>& list = t.splits();
+ for(it_s = list.begin(); it_s != list.end(); ++it_s) {
+ if(referencesClosedAccount(*it_s))
+ break;
+ }
+ return it_s != list.end();
+}
+
+bool MyMoneyFile::referencesClosedAccount(const MyMoneySplit& s) const
+{
+ if(s.accountId().isEmpty())
+ return false;
+
+ try {
+ return account(s.accountId()).isClosed();
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+ return false;
+}
+
+MyMoneyFileTransaction::MyMoneyFileTransaction() :
+ m_isNested(MyMoneyFile::instance()->hasTransaction()),
+ m_needRollback(!m_isNested)
+{
+ if(!m_isNested)
+ MyMoneyFile::instance()->startTransaction();
+}
+
+MyMoneyFileTransaction::~MyMoneyFileTransaction()
+{
+ rollback();
+}
+
+void MyMoneyFileTransaction::restart(void)
+{
+ rollback();
+
+ m_needRollback = !m_isNested;
+ if(!m_isNested)
+ MyMoneyFile::instance()->startTransaction();
+}
+
+void MyMoneyFileTransaction::commit(void)
+{
+ if(!m_isNested)
+ MyMoneyFile::instance()->commitTransaction();
+ m_needRollback = false;
+}
+
+void MyMoneyFileTransaction::rollback(void)
+{
+ if(m_needRollback)
+ MyMoneyFile::instance()->rollbackTransaction();
+ m_needRollback = false;
+}
+
+
+#include "mymoneyfile.moc"
diff --git a/kmymoney2/mymoney/mymoneyfile.h b/kmymoney2/mymoney/mymoneyfile.h
new file mode 100644
index 0000000..39552c9
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyfile.h
@@ -0,0 +1,1470 @@
+/***************************************************************************
+ mymoneyfile.h
+ -------------------
+ copyright : (C) 2002, 2007 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYFILE_H
+#define MYMONEYFILE_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qobject.h>
+#include <qstring.h>
+#include <qmap.h>
+#include <qvaluelist.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include <kmymoney/imymoneystorage.h>
+#include <kmymoney/mymoneyexception.h>
+#include <kmymoney/mymoneyutils.h>
+#include <kmymoney/mymoneyinstitution.h>
+#include <kmymoney/mymoneyaccount.h>
+#include <kmymoney/mymoneytransaction.h>
+#include <kmymoney/mymoneypayee.h>
+#include <kmymoney/mymoneykeyvaluecontainer.h>
+#include <kmymoney/mymoneysecurity.h>
+#include <kmymoney/mymoneyprice.h>
+#include <kmymoney/mymoneyreport.h>
+#include <kmymoney/mymoneybudget.h>
+#include <kmymoney/mymoneyscheduled.h>
+#include <kmymoney/export.h>
+
+/**
+ * @author Thomas Baumgart, Michael Edwardes, Kevin Tambascio
+ */
+
+class IMyMoneyStorage;
+class MyMoneyTransactionFilter;
+
+/**
+ * This class represents the interface to the MyMoney engine.
+ * For historical reasons it is still called MyMoneyFile.
+ * It is implemented using the singleton pattern and thus only
+ * exists once for each running instance of an application.
+ *
+ * The instance of the MyMoneyFile object is accessed as follows:
+ *
+ * @code
+ * MyMoneyFile *file = MyMoneyFile::instance();
+ * file->anyMemberFunction();
+ * @endcode
+ *
+ * The first line of the above code creates a unique MyMoneyFile
+ * object if it is called for the first time ever. All subsequent
+ * calls to this functions return a pointer to the object created
+ * during the first call.
+ *
+ * As the MyMoneyFile object represents the business logic, a storage
+ * manager must be attached to it. This mechanism allows to use different
+ * access methods to store the objects. The interface to access such an
+ * storage manager is defined in the class IMyMoneyStorage. The methods
+ * attachStorage() and detachStorage() are used to attach/detach a
+ * storage manager object. The following code can be used to create a
+ * functional MyMoneyFile instance:
+ *
+ * @code
+ * IMyMoneyStorage *storage = ....
+ * MyMoneyFile *file = MyMoneyFile::instance();
+ * file->attachStorage(storage);
+ * @endcode
+ *
+ * The methods addAccount(), modifyAccount() and removeAccount() implement the
+ * general account maintenance functions. The method reparentAccount() is
+ * available to move an account from one superordinate account to another.
+ * account() and accountList() are used to retrieve a single instance or a
+ * QValueList of MyMoneyAccount objects.
+ *
+ * The methods addInstitution(), modifyInstitution() and removeInstitution()
+ * implement the general institution maintenance functions. institution() and
+ * institutionList() are used to retrieve a single instance or a
+ * QValueList of MyMoneyInstitution objects.
+ *
+ * The methods addPayee(), modifyPayee() and removePayee()
+ * implement the general institution maintenance functions.
+ * payee() and payeeList() are used to retrieve a single instance or a
+ * QValueList of MyMoneyPayee objects.
+ *
+ * The methods addTransaction(), modifyTransaction() and removeTransaction()
+ * implement the general transaction maintenance functions.
+ * transaction() and transactionList() are used to retrieve
+ * a single instance or a QValueList of MyMoneyTransaction objects.
+ *
+ * The methods addSecurity(), modifySecurity() and removeSecurity()
+ * implement the general access to equities held in the engine.
+ *
+ * The methods addCurrency(), modifyCurrency() and removeCurrency()
+ * implement the general access to multiple currencies held in the engine.
+ * The methods baseCurrency() and setBaseCurrency() allow to retrieve/set
+ * the currency selected by the user as base currency. If a currency
+ * reference is emtpy, it will usually be interpreted as baseCurrency().
+ *
+ * The methods liability(), asset(), expense(), income() and equity() are
+ * used to retrieve the five standard accounts. isStandardAccount()
+ * checks if a given accountId references one of the or not.
+ * setAccountName() is used to specify a name for the standard accounts
+ * from the GUI.
+ *
+ * The MyMoneyFile object emits the dataChanged() signal when data
+ * has been changed.
+ *
+ * For abritrary values that have to be stored with the storage object
+ * but are of importance to the application only, the object is derived
+ * for MyMoneyKeyValueContainer which provides a container to store
+ * these values indexed by an alphanumeric key.
+ *
+ * @exception MyMoneyException is thrown whenever an error occurs
+ * while the engine code is running. The MyMoneyException:: object
+ * describes the problem.
+ */
+class KMYMONEY_EXPORT MyMoneyFile : public QObject
+{
+ Q_OBJECT
+public:
+
+ class MyMoneyNotifier
+ {
+ public:
+ MyMoneyNotifier(MyMoneyFile* file) { m_file = file; m_file->clearNotification(); };
+ ~MyMoneyNotifier() { m_file->notify(); };
+ private:
+ MyMoneyFile* m_file;
+ };
+
+ friend class MyMoneyNotifier;
+
+ /**
+ * This is the function to access the MyMoneyFile object.
+ * It returns a pointer to the single instance of the object.
+ */
+ static inline MyMoneyFile* instance() { return &file; }
+
+ /**
+ * This is the destructor for any MyMoneyFile object
+ */
+ ~MyMoneyFile();
+
+ /**
+ * @deprecated This is a convenience constructor. Do not use it anymore.
+ * It will be deprecated in a future version of the engine.
+ *
+ * @param storage pointer to object that implements the IMyMoneyStorage
+ * interface.
+ */
+ MyMoneyFile(IMyMoneyStorage *storage);
+
+ // general get functions
+ const MyMoneyPayee user(void) const;
+
+ // general set functions
+ void setUser(const MyMoneyPayee& user);
+
+ /**
+ * This method is used to attach a storage object to the MyMoneyFile object
+ * Without an attached storage object, the MyMoneyFile object is
+ * of no use.
+ *
+ * After successful completion, the dataChanged() signal is emitted.
+ *
+ * In case of an error condition, an exception is thrown.
+ * The following error conditions are checked:
+ *
+ * - @a storage is not equal to 0
+ * - there is no other @a storage object attached (use detachStorage()
+ * to revert the attachStorage() operation.
+ *
+ * @param storage pointer to object that implements the IMyMoneyStorage
+ * interface.
+ *
+ * @sa detachStorage()
+ */
+ void attachStorage(IMyMoneyStorage* const storage);
+
+ /**
+ * This method is used to detach a previously attached storage
+ * object from the MyMoneyFile object. If no storage object
+ * is attached to the engine, this is a NOP.
+ *
+ * @param storage pointer to object that implements the IMyMoneyStorage
+ * interface.
+ *
+ * @sa attachStorage()
+ */
+ void detachStorage(IMyMoneyStorage* const storage = 0);
+
+ /**
+ * This method returns whether a storage is currently attached to
+ * the engine or not.
+ *
+ * @return true if storage object is attached, false otherwise
+ */
+ bool storageAttached(void) const { return m_storage != 0; };
+
+ /**
+ * This method returns a pointer to the storage object
+ *
+ * @return const pointer to the current attached storage object.
+ * If no object is attached, returns 0.
+ */
+ IMyMoneyStorage* storage(void) const { return m_storage; };
+
+ /**
+ * This method must be called before any single change or a series of changes
+ * in the underlying storage area is performed.
+ * Once all changes are complete (i.e. the transaction is completed),
+ * commitTransaction() must be called to finalize all changes. If an error occurs
+ * during the processing of the changes call rollbackTransaction() to undo the
+ * changes done so far.
+ */
+ void startTransaction(void);
+
+ /**
+ * This method returns whether a transaction has been started (@a true)
+ * or not (@a false).
+ */
+ bool hasTransaction(void) const;
+
+ /**
+ * @sa startTransaction()
+ */
+ void commitTransaction(void);
+
+ /**
+ * @sa startTransaction()
+ */
+ void rollbackTransaction(void);
+
+ /**
+ * This method is used to return the standard liability account
+ * @return MyMoneyAccount liability account(group)
+ */
+ const MyMoneyAccount& liability(void) const;
+
+ /**
+ * This method is used to return the standard asset account
+ * @return MyMoneyAccount asset account(group)
+ */
+ const MyMoneyAccount& asset(void) const;
+
+ /**
+ * This method is used to return the standard expense account
+ * @return MyMoneyAccount expense account(group)
+ */
+ const MyMoneyAccount& expense(void) const;
+
+ /**
+ * This method is used to return the standard income account
+ * @return MyMoneyAccount income account(group)
+ */
+ const MyMoneyAccount& income(void) const;
+
+ /**
+ * This method is used to return the standard equity account
+ * @return MyMoneyAccount equity account(group)
+ */
+ const MyMoneyAccount& equity(void) const;
+
+ /**
+ * This method returns the account information for the opening
+ * balances account for the given @p security. If the respective
+ * account does not exist, it will be created. The name is constructed
+ * using MyMoneyFile::OpeningBalancesPrefix and appending " (xxx)" in
+ * case the @p security is not the baseCurrency(). The account created
+ * will be a sub-account of the standard equity account provided by equity().
+ *
+ * @param security Security for which the account is searched
+ *
+ * @return The opening balance account
+ *
+ * @note No notifications will be sent!
+ */
+ const MyMoneyAccount openingBalanceAccount(const MyMoneySecurity& security);
+
+ /**
+ * This method is essentially the same as the above, except it works on
+ * const objects. If there is no opening balance account, this method
+ * WILL NOT create one. Instead it will thrown an exception.
+ *
+ * @param security Security for which the account is searched
+ *
+ * @return The opening balance account
+ *
+ * @note No notifications will be sent!
+ */
+ const MyMoneyAccount openingBalanceAccount(const MyMoneySecurity& security) const;
+
+ /**
+ * Create an opening balance transaction for the account @p acc
+ * with a value of @p balance. If the corresponding opening balance account
+ * for the account's currency does not exist it will be created. If it exists
+ * and it's opening date is later than the opening date of @p acc,
+ * the opening date of the opening balances account will be adjusted to the
+ * one of @p acc.
+ *
+ * @param acc reference to account for which the opening balance transaction
+ * should be created
+ * @param balance reference to the value of the opening balance transaction
+ *
+ * @returns The created MyMoneyTransaction object. In case no transaction has been
+ * created, the id of the object is empty.
+ */
+ MyMoneyTransaction createOpeningBalanceTransaction(const MyMoneyAccount& acc, const MyMoneyMoney& balance);
+
+ /**
+ * Retrieve the opening balance transaction for the account @p acc.
+ * If there is no opening balance transaction, QString() will be returned.
+ *
+ * @param acc reference to account for which the opening balance transaction
+ * should be retrieved
+ * @return QString id for the transaction, or QString() if no transaction exists
+ */
+ QString openingBalanceTransaction(const MyMoneyAccount& acc) const;
+
+ /**
+ * This method returns an indicator if the MyMoneyFile object has been
+ * changed after it has last been saved to permanent storage.
+ *
+ * @return true if changed, false if not
+ */
+ bool dirty(void) const;
+
+ /**
+ * This method is used to force the attached storage object to
+ * be dirty. This is used by the application to re-set the dirty
+ * flag after a failed upload to a server when the save operation
+ * to a local temp file was OK.
+ */
+ void setDirty(void) const;
+
+ /**
+ * Adds an institution to the file-global institution pool. A
+ * respective institution-ID will be generated for this object.
+ * The ID is stored as QString in the object passed as argument.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param institution The complete institution information in a
+ * MyMoneyInstitution object
+ */
+ void addInstitution(MyMoneyInstitution& institution);
+
+ /**
+ * Modifies an already existing institution in the file global
+ * institution pool.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param institution The complete new institution information
+ */
+ void modifyInstitution(const MyMoneyInstitution& institution);
+
+ /**
+ * Deletes an existing institution from the file global institution pool
+ * Also modifies the accounts that reference this institution as
+ * their institution.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param institution institution to be deleted.
+ */
+ void removeInstitution(const MyMoneyInstitution& institution);
+
+ /**
+ * Adds an account to the file-global account pool. A respective
+ * account-ID will be generated within this record. The modified
+ * members of @a account will be updated.
+ *
+ * A few parameters of the account to be added are checked against
+ * the following conditions. If they do not match, an exception is
+ * thrown.
+ *
+ * An account must match the following conditions:
+ *
+ * a) the account must have a name with length > 0
+ * b) the account must not have an id assigned
+ * c) the transaction list must be empty
+ * d) the account must not have any sub-ordinate accounts
+ * e) the account must have no parent account
+ * f) the account must not have any reference to a MyMoneyFile object
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param account The complete account information in a MyMoneyAccount object
+ * @param parent The complete account information of the parent account
+ */
+ void addAccount(MyMoneyAccount& account, MyMoneyAccount& parent);
+
+ /**
+ * Modifies an already existing account in the file global account pool.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param account reference to the new account information
+ */
+ void modifyAccount(const MyMoneyAccount& account);
+
+ /**
+ * This method re-parents an existing account
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param account MyMoneyAccount reference to account to be re-parented
+ * @param parent MyMoneyAccount reference to new parent account
+ */
+ void reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent);
+
+ /**
+ * moves splits from one account to another
+ *
+ * @param oldAccount id of the current account
+ * @param newAccount if of the new account
+ *
+ * @return the number of modified splits
+ */
+ unsigned int moveSplits(const QString& oldAccount, const QString& newAccount);
+
+ /**
+ * This method is used to determince, if the account with the
+ * given ID is referenced by any split in m_transactionList.
+ *
+ * @param id id of the account to be checked for
+ * @return true if account is referenced, false otherwise
+ */
+ bool hasActiveSplits(const QString& id) const;
+
+ /**
+ * This method is used to check whether a given
+ * account id references one of the standard accounts or not.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param id account id
+ * @return true if account-id is one of the standards, false otherwise
+ */
+ bool isStandardAccount(const QString& id) const;
+
+ /**
+ * Returns @a true, if transaction @p t is a transfer transaction.
+ * A transfer transaction has two splits, both referencing either
+ * an asset, a liability or an equity account.
+ */
+ bool isTransfer(const MyMoneyTransaction& t) const;
+
+ /**
+ * This method is used to set the name for the specified standard account
+ * within the storage area. An exception will be thrown, if an error
+ * occurs
+ *
+ * @param id QString reference to one of the standard accounts.
+ * @param name QString reference to the name to be set
+ *
+ */
+ void setAccountName(const QString& id, const QString& name) const;
+
+ /**
+ * Deletes an existing account from the file global account pool
+ * This method only allows to remove accounts that are not
+ * referenced by any split. Use moveSplits() to move splits
+ * to another account. An exception is thrown in case of a
+ * problem.
+ *
+ * @param account reference to the account to be deleted.
+ */
+ void removeAccount(const MyMoneyAccount& account);
+
+ /**
+ * Deletes existing accounts and their subaccounts recursivly
+ * from the global account pool.
+ * This method expects that all accounts and their subaccounts
+ * are no longer assigned to any transactions or splits.
+ * An exception is thrown in case of a problem deleting an account.
+ *
+ * The optional parameter level is used to keep track of the recursion level.
+ * If the recursion level exceeds 100 (some arbitrary number which seems a good
+ * maximum), an exception is thrown.
+ *
+ * @param account_list Reference to a list of account IDs to be deleted.
+ * @param level Parameter to keep track of recursion level (do not pass a value here).
+ */
+ void removeAccountList(const QStringList& account_list, unsigned int level = 0);
+
+ /**
+ * This member function checks all accounts identified by account_list
+ * and their subaccounts wether they are assigned to transactions/splits or not.
+ * The function calls itself recursively with the list of sub-accounts of
+ * the currently processed account.
+ *
+ * The optional parameter level is used to keep track of the recursion level.
+ * If the recursion level exceeds 100 (some arbitrary number which seems a good
+ * maximum), an exception is thrown.
+ *
+ * @param account_list A QStringList with account IDs that need to be checked.
+ * @param level (optional) Optional parameter to indicate recursion level.
+ * @return Returns 'false' if at least one account has been found that
+ * is still referenced by a transaction.
+ */
+ bool hasOnlyUnusedAccounts(const QStringList& account_list, unsigned int level = 0);
+
+ /**
+ * Adds a transaction to the file-global transaction pool. A respective
+ * transaction-ID will be generated for this object. The ID is stored
+ * as QString in the object passed as argument.
+ * Splits must reference valid accounts and valid payees. The payee
+ * id can be empty.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param transaction reference to the transaction
+ */
+ void addTransaction(MyMoneyTransaction& transaction);
+
+ /**
+ * This method is used to update a specific transaction in the
+ * transaction pool of the MyMoneyFile object.
+ * Splits must reference valid accounts and valid payees. The payee
+ * id can be empty.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param transaction reference to transaction to be changed
+ */
+ void modifyTransaction(const MyMoneyTransaction& transaction);
+
+ /**
+ * This method is used to extract a transaction from the file global
+ * transaction pool through an id. In case of an invalid id, an
+ * exception will be thrown.
+ *
+ * @param id id of transaction as QString.
+ * @return reference to the requested transaction
+ */
+ const MyMoneyTransaction transaction(const QString& id) const;
+
+ /**
+ * This method is used to extract a transaction from the file global
+ * transaction pool through an index into an account.
+ *
+ * @param account id of the account as QString
+ * @param idx number of transaction in this account
+ * @return reference to MyMoneyTransaction object
+ */
+ const MyMoneyTransaction transaction(const QString& account, const int idx) const;
+
+ /**
+ * This method is used to pull a list of transactions from the file
+ * global transaction pool. It returns all those transactions
+ * that match the filter passed as argument. If the filter is empty,
+ * the whole journal will be returned.
+ * The list returned is sorted according to the transactions posting date.
+ * If more than one transaction exists for the same date, the order among
+ * them is undefined.
+ *
+ * @param filter MyMoneyTransactionFilter object with the match criteria
+ *
+ * @return set of transactions in form of a QValueList<MyMoneyTransaction>
+ */
+ const QValueList<MyMoneyTransaction> transactionList(MyMoneyTransactionFilter& filter) const;
+
+ void transactionList(QValueList<MyMoneyTransaction>& list, MyMoneyTransactionFilter& filter) const;
+
+ void transactionList(QValueList<QPair<MyMoneyTransaction, MyMoneySplit> >& list, MyMoneyTransactionFilter& filter) const;
+
+ /**
+ * This method is used to remove a transaction from the transaction
+ * pool (journal).
+ *
+ * @param transaction const reference to transaction to be deleted
+ */
+ void removeTransaction(const MyMoneyTransaction& transaction);
+
+ /**
+ * This method is used to return the actual balance of an account
+ * without it's sub-ordinate accounts. If a @p date is presented,
+ * the balance at the beginning of this date (not including any
+ * transaction on this date) is returned. Otherwise all recorded
+ * transactions are included in the balance.
+ *
+ * @param id id of the account in question
+ * @param date return balance for specific date (default = QDate())
+ * @return balance of the account as MyMoneyMoney object
+ */
+ const MyMoneyMoney balance(const QString& id, const QDate& date = QDate()) const;
+
+ /**
+ * This method is used to return the actual balance of an account
+ * including it's sub-ordinate accounts. If a @p date is presented,
+ * the balance at the beginning of this date (not including any
+ * transaction on this date) is returned. Otherwise all recorded
+ * transactions are included in the balance.
+ *
+ * @param id id of the account in question
+ * @param date return balance for specific date (default = QDate())
+ * @return balance of the account as MyMoneyMoney object
+ */
+ const MyMoneyMoney totalBalance(const QString& id, const QDate& date = QDate()) const;
+
+ /**
+ * This method returns the number of transactions currently known to file
+ * in the range 0..MAXUINT
+ *
+ * @param account QString reference to account id. If account is empty
+ + all transactions (the journal) will be counted. If account
+ * is not empty it returns the number of transactions
+ * that have splits in this account.
+ *
+ * @return number of transactions in journal/account
+ */
+ unsigned int transactionCount(const QString& account = QString()) const;
+
+ /**
+ * This method returns a QMap filled with the number of transactions
+ * per account. The account id serves as index into the map. If one
+ * needs to have all transactionCounts() for many accounts, this method
+ * is faster than calling transactionCount(const QString& account) many
+ * times.
+ *
+ * @return QMap with numbers of transactions per account
+ */
+ const QMap<QString, unsigned long> transactionCountMap(void) const;
+
+ /**
+ * This method returns the number of institutions currently known to file
+ * in the range 0..MAXUINT
+ *
+ * @return number of institutions known to file
+ */
+ unsigned int institutionCount(void) const;
+
+ /**
+ * This method returns the number of accounts currently known to file
+ * in the range 0..MAXUINT
+ *
+ * @return number of accounts currently known inside a MyMoneyFile object
+ */
+ unsigned int accountCount(void) const;
+
+ /**
+ * Returns the institution of a given ID
+ *
+ * @param id id of the institution to locate
+ * @return MyMoneyInstitution object filled with data. If the institution
+ * could not be found, an exception will be thrown
+ */
+ const MyMoneyInstitution& institution(const QString& id) const;
+
+ /**
+ * This method returns a list of the institutions
+ * inside a MyMoneyFile object
+ *
+ * @param list reference to the list. It will be cleared by this method first
+ */
+ void institutionList(QValueList<MyMoneyInstitution>& list) const;
+
+ /**
+ * This method returns a list of the institutions
+ * inside a MyMoneyFile object. This is a convenience method
+ * to the one above
+ *
+ * @return QValueList containing the institution objects
+ */
+ const QValueList<MyMoneyInstitution> institutionList(void) const;
+
+ /**
+ * Returns the account addressed by its id.
+ *
+ * @param id id of the account to locate.
+ * @return MyMoneyAccount object carrying the @p id. An exception is thrown
+ * if the id is unknown
+ */
+ const MyMoneyAccount& account(const QString& id) const;
+
+ /**
+ * Returns the account addressed by its name.
+ *
+ * @param name name of the account to locate.
+ * @return First MyMoneyAccount object found carrying the @p name.
+ * An empty MyMoneyAccount object will be returned if the name is not found.
+ */
+ const MyMoneyAccount& accountByName(const QString& name) const;
+
+ /**
+ * Returns the sub-account addressed by its name.
+ *
+ * @param acc account to search in
+ * @param name name of the account to locate.
+ * @return First MyMoneyAccount object found carrying the @p name.
+ * An empty MyMoneyAccount object will be returned if the name is not found.
+ */
+ const MyMoneyAccount& subAccountByName(const MyMoneyAccount& acc, const QString& name) const;
+
+ /**
+ * This method returns a list of accounts inside a MyMoneyFile object.
+ * An optional parameter is a list of id's. If this list is emtpy (the default)
+ * the returned list contains all accounts, otherwise only those referenced
+ * in the id-list.
+ *
+ * @param list reference to QValueList receiving the account objects
+ * @param idlist QStringList of account ids of those accounts that
+ * should be returned. If this list is empty, all accounts
+ * currently known will be returned.
+ *
+ * @param recursive if @p true, then recurse in all found accounts. The default is @p false
+ */
+ void accountList(QValueList<MyMoneyAccount>& list, const QStringList& idlist = QStringList(), const bool recursive = false) const;
+
+ /**
+ * This method is used to convert an account id to a string representation
+ * of the names which can be used as a category description. If the account
+ * is part of a hierarchy, the category name will be the concatenation of
+ * the single account names seperated by MyMoneyAccount::AccountSeperator.
+ *
+ * @param accountId QString reference of the account's id
+ * @param includeStandardAccounts if true, the standard top account will be part
+ * of the name, otherwise it will not be included (default is @c false)
+ *
+ * @return QString of the constructed name.
+ */
+ QString accountToCategory(const QString& accountId, bool includeStandardAccounts = false) const;
+
+ /**
+ * This method is used to convert a string representing a category to
+ * an account id. A category can be the concatenation of multiple accounts
+ * representing a hierarchy of accounts. They have to be seperated by
+ * MyMoneyAccount::AccountSeperator.
+ *
+ * @param category const reference to QString containing the category
+ * @param type account type if a specific type is required (defaults to UnknownAccountType)
+ *
+ * @return QString of the corresponding account. If account was not found
+ * the return value will be an empty string.
+ */
+ QString categoryToAccount(const QString& category, MyMoneyAccount::accountTypeE type = MyMoneyAccount::UnknownAccountType) const;
+
+ /**
+ * This method is used to convert a string representing an asset or
+ * liability account to an account id. An account name can be the
+ * concatenation of multiple accounts representing a hierarchy of
+ * accounts. They have to be seperated by MyMoneyAccount::AccountSeperator.
+ *
+ * @param name const reference to QString containing the account name
+ *
+ * @return QString of the corresponding account. If account was not found
+ * the return value will be an empty string.
+ */
+ QString nameToAccount(const QString& name) const;
+
+ /**
+ * This method is used to extract the parent part of an account hierarchy
+ * name who's parts are seperated by MyMoneyAccount::AccountSeperator.
+ *
+ * @param name full account name
+ * @return parent name (full account name excluding the last part)
+ */
+ QString parentName(const QString& name) const;
+
+ /**
+ * This method is used to create a new payee
+ *
+ * An exception will be thrown upon error conditions
+ *
+ * @param payee MyMoneyPayee reference to payee information
+ */
+ void addPayee(MyMoneyPayee& payee);
+
+ /**
+ * This method is used to retrieve information about a payee
+ * An exception will be thrown upon error conditions.
+ *
+ * @param id QString reference to id of payee
+ *
+ * @return MyMoneyPayee object of payee
+ */
+ const MyMoneyPayee& payee(const QString& id) const;
+
+ /**
+ * This method is used to retrieve the id to a corresponding
+ * name of a payee/receiver.
+ * An exception will be thrown upon error conditions.
+ *
+ * @param payee QString reference to name of payee
+ *
+ * @return MyMoneyPayee object of payee
+ */
+ const MyMoneyPayee& payeeByName(const QString& payee) const;
+
+ /**
+ * This method is used to modify an existing payee
+ *
+ * An exception will be thrown upon error conditions
+ *
+ * @param payee MyMoneyPayee reference to payee information
+ */
+ void modifyPayee(const MyMoneyPayee& payee);
+
+ /**
+ * This method is used to remove an existing payee.
+ * An error condition occurs, if the payee is still referenced
+ * by a split.
+ *
+ * An exception will be thrown upon error conditions
+ *
+ * @param payee MyMoneyPayee reference to payee information
+ */
+ void removePayee(const MyMoneyPayee& payee);
+
+ /**
+ * This method returns a list of the payees
+ * inside a MyMoneyStorage object
+ *
+ * @return QValueList<MyMoneyPayee> containing the payee information
+ */
+ const QValueList<MyMoneyPayee> payeeList(void) const;
+
+ /**
+ * This method is used to extract a value from the storage's
+ * KeyValueContainer. For details see MyMoneyKeyValueContainer::value().
+ *
+ * @param key const reference to QString containing the key
+ * @return QString containing the value
+ */
+ QString value(const QString& key) const;
+
+ /**
+ * This method is used to set a value in the storage's
+ * KeyValueContainer. For details see MyMoneyKeyValueContainer::setValue().
+ *
+ * @param key const reference to QString containing the key
+ * @param val const reference to QString containing the value
+ *
+ * @note Keys starting with the leadin @p kmm- are reserved for internal use
+ * by the MyMoneyFile object.
+ */
+ void setValue(const QString& key, const QString& val);
+
+ /**
+ * This method is used to delete a key-value-pair from the
+ * storage's KeyValueContainer identified by the parameter
+ * @p key. For details see MyMoneyKeyValueContainer::deletePair().
+ *
+ * @param key const reference to QString containing the key
+ */
+ void deletePair(const QString& key);
+
+ /**
+ * This method is used to add a scheduled transaction to the engine.
+ * It must be sure, that the id of the object is not filled. When the
+ * method returns to the caller, the id will be filled with the
+ * newly created object id value.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param sched reference to the MyMoneySchedule object
+ */
+ void addSchedule(MyMoneySchedule& sched);
+
+ /**
+ * This method is used to modify an existing MyMoneySchedule
+ * object. Therefor, the id attribute of the object must be set.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param sched const reference to the MyMoneySchedule object to be updated
+ */
+ void modifySchedule(const MyMoneySchedule& sched);
+
+ /**
+ * This method is used to remove an existing MyMoneySchedule object
+ * from the engine. The id attribute of the object must be set.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param sched const reference to the MyMoneySchedule object to be updated
+ */
+ void removeSchedule(const MyMoneySchedule& sched);
+
+ /**
+ * This method is used to retrieve a single MyMoneySchedule object.
+ * The id of the object must be supplied in the parameter @p id.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param id QString containing the id of the MyMoneySchedule object
+ * @return MyMoneySchedule object
+ */
+ const MyMoneySchedule schedule(const QString& id) const;
+
+ /**
+ * This method is used to extract a list of scheduled transactions
+ * according to the filter criteria passed as arguments.
+ *
+ * @param accountId only search for scheduled transactions that reference
+ * account @p accountId. If accountId is the empty string,
+ * this filter is off. Default is @p QString().
+ * @param type only schedules of type @p type are searched for.
+ * See MyMoneySchedule::typeE for details.
+ * Default is MyMoneySchedule::TYPE_ANY
+ * @param occurence only schedules of occurence type @p occurence are searched for.
+ * See MyMoneySchedule::occurenceE for details.
+ * Default is MyMoneySchedule::OCCUR_ANY
+ * @param paymentType only schedules of payment method @p paymentType
+ * are searched for.
+ * See MyMoneySchedule::paymentTypeE for details.
+ * Default is MyMoneySchedule::STYPE_ANY
+ * @param startDate only schedules with payment dates after @p startDate
+ * are searched for. Default is all dates (QDate()).
+ * @param endDate only schedules with payment dates ending prior to @p endDate
+ * are searched for. Default is all dates (QDate()).
+ * @param overdue if true, only those schedules that are overdue are
+ * searched for. Default is false (all schedules will be returned).
+ *
+ * @return const QValueList<MyMoneySchedule> list of schedule objects.
+ */
+ const QValueList<MyMoneySchedule> scheduleList(const QString& accountId = QString(),
+ const MyMoneySchedule::typeE type = MyMoneySchedule::TYPE_ANY,
+ const MyMoneySchedule::occurenceE occurence = MyMoneySchedule::OCCUR_ANY,
+ const MyMoneySchedule::paymentTypeE paymentType = MyMoneySchedule::STYPE_ANY,
+ const QDate& startDate = QDate(),
+ const QDate& endDate = QDate(),
+ const bool overdue = false) const;
+
+ const QStringList consistencyCheck(void);
+
+ /**
+ * MyMoneyFile::OpeningBalancesPrefix is a special string used
+ * to generate the name for opening balances accounts. See openingBalanceAccount()
+ * for details.
+ */
+ static const QString OpeningBalancesPrefix;
+
+ /**
+ * MyMoneyFile::AccountSeperator is used as the seperator
+ * between account names to form a hierarchy.
+ */
+ static const QString AccountSeperator;
+
+ /**
+ * createCategory creates a category from a text name.
+ *
+ * The whole account hierarchy is created if it doesnt
+ * already exist. e.g if name = Bills:Credit Card and
+ * base = expense(), Bills will first be checked to see if
+ * it exists and created if not. Credit Card will then
+ * be created with Bills as it's parent. The Credit Card account
+ * will have it's id returned.
+ *
+ * @param base The base account (expense or income)
+ * @param name The category to create
+ *
+ * @return The category account id or empty on error.
+ *
+ * @exception An exception will be thrown, if @p base is not equal
+ * expense() or income().
+ **/
+ QString createCategory(const MyMoneyAccount& base, const QString& name);
+
+ const QValueList<MyMoneySchedule> scheduleListEx( int scheduleTypes,
+ int scheduleOcurrences,
+ int schedulePaymentTypes,
+ QDate startDate,
+ const QStringList& accounts=QStringList()) const;
+
+ /**
+ * This method is used to add a new security object to the engine.
+ * The ID of the object is the trading symbol, so there is no need for an additional
+ * ID since the symbol is guaranteed to be unique.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param security reference to the MyMoneySecurity object
+ */
+ void addSecurity(MyMoneySecurity& security);
+
+ /**
+ * This method is used to modify an existing MyMoneySchedule
+ * object.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param security reference to the MyMoneySecurity object to be updated
+ */
+ void modifySecurity(const MyMoneySecurity& security);
+
+ /**
+ * This method is used to remove an existing MyMoneySecurity object
+ * from the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param security reference to the MyMoneySecurity object to be removed
+ */
+ void removeSecurity(const MyMoneySecurity& security);
+
+ /**
+ * This method is used to retrieve a single MyMoneySecurity object.
+ * The id of the object must be supplied in the parameter @p id.
+ * If no security with the given id is found, then a corresponding
+ * currency is searched. If @p id is empty, the baseCurrency() is returned.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param id QString containing the id of the MyMoneySecurity object
+ * @return MyMoneySecurity object
+ */
+ const MyMoneySecurity& security(const QString& id) const;
+
+ /**
+ * This method is used to retrieve a list of all MyMoneySecurity objects.
+ */
+ const QValueList<MyMoneySecurity> securityList(void) const;
+
+ /**
+ * This method is used to add a new currency object to the engine.
+ * The ID of the object is the trading symbol, so there is no need for an additional
+ * ID since the symbol is guaranteed to be unique.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param currency reference to the MyMoneySecurity object
+ */
+ void addCurrency(const MyMoneySecurity& currency);
+
+ /**
+ * This method is used to modify an existing MyMoneySecurity
+ * object.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param currency reference to the MyMoneySecurity object
+ */
+ void modifyCurrency(const MyMoneySecurity& currency);
+
+ /**
+ * This method is used to remove an existing MyMoneySecurity object
+ * from the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param currency reference to the MyMoneySecurity object
+ */
+ void removeCurrency(const MyMoneySecurity& currency);
+
+ /**
+ * This method is used to retrieve a single MyMoneySchedule object.
+ * The id of the object must be supplied in the parameter @p id.
+ * If @p id is empty, this method returns baseCurrency().
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param id QString containing the id of the MyMoneySchedule object
+ * @return MyMoneySchedule object
+ */
+ const MyMoneySecurity& currency(const QString& id) const;
+
+ /**
+ * This method is used to retrieve the list of all currencies
+ * known to the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @return QValueList of all MyMoneySecurity objects.
+ */
+ const QValueList<MyMoneySecurity> currencyList(void) const;
+
+ /**
+ * This method retrieves a MyMoneySecurity object representing
+ * the selected base currency. If the base currency is not
+ * selected (e.g. due to a previous call to setBaseCurrency())
+ * a standard MyMoneySecurity object will be returned. See
+ * MyMoneySecurity() for details.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @return MyMoneySecurity describing base currency
+ */
+ const MyMoneySecurity& baseCurrency(void) const;
+
+ /**
+ * This method returns the foreign currency of the given two
+ * currency ids. If second is the base currency id then @a first
+ * is returned otherwise @a second is returned.
+ */
+ const QString& foreignCurrency(const QString& first, const QString& second) const;
+
+ /**
+ * This method allows to select the base currency. It does
+ * not perform any changes to the data in the engine. It merely
+ * stores a reference to the base currency. The currency
+ * passed as argument must exist in the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param currency
+ */
+ void setBaseCurrency(const MyMoneySecurity& currency);
+
+ /**
+ * This method adds/replaces a price to/from the price list
+ */
+ void addPrice(const MyMoneyPrice& price);
+
+ /**
+ * This method removes a price from the price list
+ */
+ void removePrice(const MyMoneyPrice& price);
+
+ /**
+ * This method is used to retrieve a price for a specific security
+ * on a specific date. If there is no price for this date, the last
+ * known price for this currency is used. If no price information
+ * is available, 1.0 will be returned as price.
+ *
+ * @param fromId the id of the currency in question
+ * @param toId the id of the currency to convert to (if emtpy, baseCurrency)
+ * @param date the date for which the price should be returned (default = today)
+ * @param exactDate if true, entry for date must exist, if false any price information
+ * with a date less or equal to @p date will be returned
+ *
+ * @return price found as MyMoneyPrice object
+ * @note This throws an exception when the base currency is not set and toId is empty
+ */
+ const MyMoneyPrice price(const QString& fromId, const QString& toId = QString(), const QDate& date = QDate::currentDate(), const bool exactDate = false) const;
+
+ /**
+ * This method returns a list of all prices.
+ *
+ * @return MyMoneyPriceList of all MyMoneyPrice objects.
+ */
+ const MyMoneyPriceList priceList(void) const;
+
+ /**
+ * This method allows to interrogate the engine, if a known account
+ * with id @p id has a subaccount with the name @p name.
+ *
+ * @param id id of the account to look at
+ * @param name account name that needs to be searched force
+ * @retval true account with name @p name found as subaccounts
+ * @retval false no subaccount present with that name
+ */
+ bool hasAccount(const QString& id, const QString& name) const;
+
+ /**
+ * This method is used to retrieve the list of all reports
+ * known to the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @return QValueList of all MyMoneyReport objects.
+ */
+ const QValueList<MyMoneyReport> reportList( void ) const;
+
+ /**
+ * Adds a report to the file-global institution pool. A
+ * respective report-ID will be generated for this object.
+ * The ID is stored as QString in the object passed as argument.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param report The complete report information in a
+ * MyMoneyReport object
+ */
+ void addReport( MyMoneyReport& report );
+
+ /**
+ * Modifies an already existing report in the file global
+ * report pool.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param report The complete new report information
+ */
+ void modifyReport( const MyMoneyReport& report );
+
+ /**
+ * This method returns the number of reports currently known to file
+ * in the range 0..MAXUINT
+ *
+ * @return number of reports known to file
+ */
+ unsigned countReports( void ) const;
+
+ /**
+ * This method is used to retrieve a single MyMoneyReport object.
+ * The id of the object must be supplied in the parameter @p id.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param id QString containing the id of the MyMoneyReport object
+ * @return MyMoneyReport object
+ */
+ const MyMoneyReport report( const QString& id ) const;
+
+ /**
+ * This method is used to remove an existing MyMoneyReport object
+ * from the engine. The id attribute of the object must be set.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param report const reference to the MyMoneyReport object to be updated
+ */
+ void removeReport(const MyMoneyReport& report);
+
+ /**
+ * This method is used to retrieve the list of all budgets
+ * known to the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @return QValueList of all MyMoneyBudget objects.
+ */
+ const QValueList<MyMoneyBudget> budgetList( void ) const;
+
+ /**
+ * Adds a budget to the file-global institution pool. A
+ * respective budget-ID will be generated for this object.
+ * The ID is stored as QString in the object passed as argument.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param budget The complete budget information in a
+ * MyMoneyBudget object
+ */
+ void addBudget( MyMoneyBudget& budget );
+
+
+ /**
+ * This method is used to retrieve the id to a corresponding
+ * name of a budget.
+ * An exception will be thrown upon error conditions.
+ *
+ * @param budget QString reference to name of budget
+ *
+ * @return MyMoneyBudget refernce to object of budget
+ */
+ const MyMoneyBudget budgetByName(const QString& budget) const;
+
+
+ /**
+ * Modifies an already existing budget in the file global
+ * budget pool.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param budget The complete new budget information
+ */
+ void modifyBudget( const MyMoneyBudget& budget );
+
+ /**
+ * This method returns the number of budgets currently known to file
+ * in the range 0..MAXUINT
+ *
+ * @return number of budgets known to file
+ */
+ unsigned countBudgets( void ) const;
+
+ /**
+ * This method is used to retrieve a single MyMoneyBudget object.
+ * The id of the object must be supplied in the parameter @p id.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param id QString containing the id of the MyMoneyBudget object
+ * @return MyMoneyBudget object
+ */
+ const MyMoneyBudget budget( const QString& id ) const;
+
+ /**
+ * This method is used to remove an existing MyMoneyBudget object
+ * from the engine. The id attribute of the object must be set.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param budget const reference to the MyMoneyBudget object to be updated
+ */
+ void removeBudget(const MyMoneyBudget& budget);
+
+
+ /**
+ * This method checks, if the given @p object is referenced
+ * by another engine object.
+ *
+ * @param obj const reference to object to be checked
+ * @param skipCheck MyMoneyFileBitArray with ReferenceCheckBits set for which
+ * the check should be skipped
+ *
+ * @retval false @p object is not referenced
+ * @retval true @p institution is referenced
+ */
+ bool isReferenced(const MyMoneyObject& obj, const MyMoneyFileBitArray& skipCheck = MyMoneyFileBitArray()) const;
+
+ /**
+ * Returns true if any of the accounts referenced by the splits
+ * of transaction @a t is closed.
+ */
+ bool referencesClosedAccount(const MyMoneyTransaction& t) const;
+
+ /**
+ * Returns true if the accounts referenced by the split @a s is closed.
+ */
+ bool referencesClosedAccount(const MyMoneySplit& s) const;
+
+ /**
+ * This method checks if the given check no &p no is used in
+ * a transaction referencing account &p accId. If @p accId is empty,
+ * @p false is returned.
+ *
+ * @param accId id of account to checked
+ * @param no check number to be verified if used or not
+ * @retval false @p no is not in use
+ * @retval true @p no is already assigned
+ */
+ bool checkNoUsed(const QString& accId, const QString& no) const;
+
+ /**
+ * This method returns the highest assigned check no for
+ * account @p accId.
+ *
+ * @param accId id of account to be scanned
+ * @return highest check no. used
+ */
+ QString highestCheckNo(const QString& accId) const;
+
+ /**
+ * Clear all internal caches (used internally for performance measurements)
+ */
+ void clearCache(void);
+
+ void forceDataChanged(void) { emit dataChanged(); }
+
+ void preloadCache(void);
+
+protected:
+ /**
+ * This is the constructor for a new empty file description
+ */
+ MyMoneyFile();
+
+signals:
+ /**
+ * This signal is emitted whenever any data has been changed in the engine
+ * via any of the methods of this object
+ */
+ void dataChanged(void);
+
+private:
+ static MyMoneyFile file;
+
+ MyMoneyFile& operator=(MyMoneyFile&); // not allowed for singleton
+ MyMoneyFile(const MyMoneyFile&); // not allowed for singleton
+
+ QString locateSubAccount(const MyMoneyAccount& base, const QString& category) const;
+
+ void ensureDefaultCurrency(MyMoneyAccount& acc) const;
+
+ void warningMissingRate(const QString& fromId, const QString& toId) const;
+
+ /**
+ * This method creates an opening balances account. The name is constructed
+ * using MyMoneyFile::OpeningBalancesPrefix and appending " (xxx)" in
+ * case the @p security is not the baseCurrency(). The account created
+ * will be a sub-account of the standard equity account provided by equity().
+ *
+ * @param security Security for which the account is searched
+ */
+ const MyMoneyAccount createOpeningBalanceAccount(const MyMoneySecurity& security);
+
+ const MyMoneyAccount openingBalanceAccount_internal(const MyMoneySecurity& security) const;
+
+private:
+ /**
+ * This method is used to add an id to the list of objects
+ * to be removed from the cache. If id is empty, then nothing is added to the list.
+ *
+ * @param id id of object to be notified
+ * @param reload reload the object (@c true) or not (@c false). The default is @c true
+ * @see attach, detach
+ */
+ void addNotification(const QString& id, bool reload = true);
+
+ /**
+ * This method is used to clear the notification list
+ */
+ void clearNotification(void);
+
+ /**
+ * This method is used to clear all
+ * objects mentioned in m_notificationList from the cache.
+ */
+ void notify(void);
+
+ /**
+ * This method checks if a storage object is attached and
+ * throws and exception if not.
+ */
+ inline void checkStorage(void) const {
+ if(m_storage == 0)
+ throw new MYMONEYEXCEPTION("No storage object attached to MyMoneyFile");
+ }
+
+ /**
+ * This method checks that a transaction has been started with
+ * startTransaction() and throws an exception otherwise. Calls
+ * checkStorage() to make sure a storage object is present and attached.
+ */
+ void checkTransaction(const char* txt) const;
+
+private:
+ /**
+ * This member points to the storage strategy
+ */
+ IMyMoneyStorage *m_storage;
+
+ /// \internal d-pointer class.
+ class Private;
+ /// \internal d-pointer instance.
+ Private* const d;
+
+ static MyMoneyFile* _instance;
+};
+
+class KMYMONEY_EXPORT MyMoneyFileTransaction
+{
+public:
+ MyMoneyFileTransaction();
+ ~MyMoneyFileTransaction();
+
+ /**
+ * Commit the current transaction.
+ *
+ * @warning Make sure not to use any variable that might have been altered by
+ * the transaction. Please keep in mind, that changing transactions
+ * can also affect account objects. If you still need those variables
+ * just reload them from the engine.
+ */
+ void commit(void);
+ void rollback(void);
+ void restart(void);
+
+private:
+ bool m_isNested;
+ bool m_needRollback;
+};
+
+#endif
+
diff --git a/kmymoney2/mymoney/mymoneyfiletest.cpp b/kmymoney2/mymoney/mymoneyfiletest.cpp
new file mode 100644
index 0000000..e081fa0
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyfiletest.cpp
@@ -0,0 +1,1550 @@
+/***************************************************************************
+ mymoneyfiletest.cpp
+ -------------------
+ copyright : (C) 2002 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "mymoneyfiletest.h"
+#include <iostream>
+
+#include <memory>
+#include <unistd.h>
+#include <qfile.h>
+#include <qdatastream.h>
+
+MyMoneyFileTest:: MyMoneyFileTest () {}
+
+
+void MyMoneyFileTest::setUp () {
+ storage = new MyMoneySeqAccessMgr;
+ m = MyMoneyFile::instance();
+ m->attachStorage(storage);
+}
+
+void MyMoneyFileTest::tearDown () {
+ m->detachStorage(storage);
+ delete storage;
+}
+
+void MyMoneyFileTest::testEmptyConstructor() {
+ MyMoneyPayee user = m->user();
+
+ CPPUNIT_ASSERT(user.name().isEmpty());
+ CPPUNIT_ASSERT(user.address().isEmpty());
+ CPPUNIT_ASSERT(user.city().isEmpty());
+ CPPUNIT_ASSERT(user.state().isEmpty());
+ CPPUNIT_ASSERT(user.postcode().isEmpty());
+ CPPUNIT_ASSERT(user.telephone().isEmpty());
+ CPPUNIT_ASSERT(user.email().isEmpty());
+
+ CPPUNIT_ASSERT(m->institutionCount() == 0);
+ CPPUNIT_ASSERT(m->dirty() == false);
+ CPPUNIT_ASSERT(m->accountCount() == 5);
+}
+
+void MyMoneyFileTest::testAddOneInstitution() {
+ MyMoneyInstitution institution;
+
+ institution.setName("institution1");
+ institution.setTown("town");
+ institution.setStreet("street");
+ institution.setPostcode("postcode");
+ institution.setTelephone("telephone");
+ institution.setManager("manager");
+ institution.setSortcode("sortcode");
+
+ // MyMoneyInstitution institution_file("", institution);
+ MyMoneyInstitution institution_id("I000002", institution);
+ MyMoneyInstitution institution_noname(institution);
+ institution_noname.setName(QString());
+
+ QString id;
+
+ CPPUNIT_ASSERT(m->institutionCount() == 0);
+ storage->m_dirty = false;
+
+ MyMoneyFileTransaction ft;
+ try {
+ m->addInstitution(institution);
+ ft.commit();
+ CPPUNIT_ASSERT(institution.id() == "I000001");
+ CPPUNIT_ASSERT(m->institutionCount() == 1);
+ CPPUNIT_ASSERT(m->dirty() == true);
+ } catch(MyMoneyException *e) {
+ CPPUNIT_FAIL("Unexpected exception");
+ delete e;
+ }
+
+ ft.restart();
+ try {
+ m->addInstitution(institution_id);
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch(MyMoneyException *e) {
+ ft.commit();
+ CPPUNIT_ASSERT(m->institutionCount() == 1);
+ delete e;
+ }
+
+ ft.restart();
+ try {
+ m->addInstitution(institution_noname);
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch(MyMoneyException *e) {
+ ft.commit();
+ CPPUNIT_ASSERT(m->institutionCount() == 1);
+ delete e;
+ }
+}
+
+void MyMoneyFileTest::testAddTwoInstitutions() {
+ testAddOneInstitution();
+ MyMoneyInstitution institution;
+ institution.setName("institution2");
+ institution.setTown("town");
+ institution.setStreet("street");
+ institution.setPostcode("postcode");
+ institution.setTelephone("telephone");
+ institution.setManager("manager");
+ institution.setSortcode("sortcode");
+
+ QString id;
+
+ storage->m_dirty = false;
+ MyMoneyFileTransaction ft;
+ try {
+ m->addInstitution(institution);
+ ft.commit();
+
+ CPPUNIT_ASSERT(institution.id() == "I000002");
+ CPPUNIT_ASSERT(m->institutionCount() == 2);
+ CPPUNIT_ASSERT(m->dirty() == true);
+ } catch(MyMoneyException *e) {
+ CPPUNIT_FAIL("Unexpected exception");
+ delete e;
+ }
+
+ storage->m_dirty = false;
+
+ try {
+ institution = m->institution("I000001");
+ CPPUNIT_ASSERT(institution.id() == "I000001");
+ CPPUNIT_ASSERT(m->institutionCount() == 2);
+ CPPUNIT_ASSERT(m->dirty() == false);
+
+ institution = m->institution("I000002");
+ CPPUNIT_ASSERT(institution.id() == "I000002");
+ CPPUNIT_ASSERT(m->institutionCount() == 2);
+ CPPUNIT_ASSERT(m->dirty() == false);
+ } catch (MyMoneyException *e) {
+ CPPUNIT_FAIL("Unexpected exception");
+ delete e;
+ }
+}
+
+void MyMoneyFileTest::testRemoveInstitution() {
+ testAddTwoInstitutions();
+
+ MyMoneyInstitution i;
+
+ CPPUNIT_ASSERT(m->institutionCount() == 2);
+
+ i = m->institution("I000001");
+ CPPUNIT_ASSERT(i.id() == "I000001");
+ CPPUNIT_ASSERT(i.accountCount() == 0);
+
+ storage->m_dirty = false;
+ MyMoneyFileTransaction ft;
+ try {
+ m->removeInstitution(i);
+ ft.commit();
+ CPPUNIT_ASSERT(m->institutionCount() == 1);
+ CPPUNIT_ASSERT(m->dirty() == true);
+ } catch (MyMoneyException *e) {
+ CPPUNIT_FAIL("Unexpected exception");
+ delete e;
+ }
+
+ storage->m_dirty = false;
+
+ try {
+ m->institution("I000001");
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch(MyMoneyException *e) {
+ CPPUNIT_ASSERT(m->institutionCount() == 1);
+ CPPUNIT_ASSERT(m->dirty() == false);
+ delete e;
+ }
+
+ ft.restart();
+ try {
+ m->removeInstitution(i);
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch(MyMoneyException *e) {
+ ft.commit();
+ CPPUNIT_ASSERT(m->institutionCount() == 1);
+ CPPUNIT_ASSERT(m->dirty() == false);
+ delete e;
+ }
+}
+
+void MyMoneyFileTest::testInstitutionRetrieval () {
+
+ testAddOneInstitution();
+
+ storage->m_dirty = false;
+
+ MyMoneyInstitution institution;
+
+ CPPUNIT_ASSERT(m->institutionCount() == 1);
+
+ try {
+ institution = m->institution("I000001");
+ CPPUNIT_ASSERT(institution.id() == "I000001");
+ CPPUNIT_ASSERT(m->institutionCount() == 1);
+ } catch (MyMoneyException *e) {
+ CPPUNIT_FAIL("Unexpected exception");
+ delete e;
+ }
+
+ try {
+ institution = m->institution("I000002");
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch(MyMoneyException *e) {
+ CPPUNIT_ASSERT(m->institutionCount() == 1);
+ delete e;
+ }
+
+ CPPUNIT_ASSERT(m->dirty() == false);
+}
+
+void MyMoneyFileTest::testInstitutionListRetrieval () {
+ QValueList<MyMoneyInstitution> list;
+
+ storage->m_dirty = false;
+ list = m->institutionList();
+ CPPUNIT_ASSERT(m->dirty() == false);
+ CPPUNIT_ASSERT(list.count() == 0);
+
+ testAddTwoInstitutions();
+
+ storage->m_dirty = false;
+ list = m->institutionList();
+ CPPUNIT_ASSERT(m->dirty() == false);
+ CPPUNIT_ASSERT(list.count() == 2);
+
+ QValueList<MyMoneyInstitution>::ConstIterator it;
+ it = list.begin();
+
+ CPPUNIT_ASSERT((*it).name() == "institution1");
+ ++it;
+ CPPUNIT_ASSERT((*it).name() == "institution2");
+ ++it;
+ CPPUNIT_ASSERT(it == list.end());
+}
+
+void MyMoneyFileTest::testInstitutionModify() {
+ testAddTwoInstitutions();
+ MyMoneyInstitution institution;
+
+ institution = m->institution("I000001");
+ institution.setStreet("new street");
+ institution.setTown("new town");
+ institution.setPostcode("new postcode");
+ institution.setTelephone("new telephone");
+ institution.setManager("new manager");
+ institution.setName("new name");
+ institution.setSortcode("new sortcode");
+
+ storage->m_dirty = false;
+
+ MyMoneyFileTransaction ft;
+ try {
+ m->modifyInstitution(institution);
+ ft.commit();
+ CPPUNIT_ASSERT(institution.id() == "I000001");
+ CPPUNIT_ASSERT(m->institutionCount() == 2);
+ CPPUNIT_ASSERT(m->dirty() == true);
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ MyMoneyInstitution newInstitution;
+ newInstitution = m->institution("I000001");
+
+ CPPUNIT_ASSERT(newInstitution.id() == "I000001");
+ CPPUNIT_ASSERT(newInstitution.street() == "new street");
+ CPPUNIT_ASSERT(newInstitution.town() == "new town");
+ CPPUNIT_ASSERT(newInstitution.postcode() == "new postcode");
+ CPPUNIT_ASSERT(newInstitution.telephone() == "new telephone");
+ CPPUNIT_ASSERT(newInstitution.manager() == "new manager");
+ CPPUNIT_ASSERT(newInstitution.name() == "new name");
+ CPPUNIT_ASSERT(newInstitution.sortcode() == "new sortcode");
+
+ storage->m_dirty = false;
+
+ ft.restart();
+ MyMoneyInstitution failInstitution2("I000003", newInstitution);
+ try {
+ m->modifyInstitution(failInstitution2);
+ CPPUNIT_FAIL("Exception expected");
+ } catch(MyMoneyException *e) {
+ ft.commit();
+ delete e;
+ CPPUNIT_ASSERT(failInstitution2.id() == "I000003");
+ CPPUNIT_ASSERT(m->institutionCount() == 2);
+ CPPUNIT_ASSERT(m->dirty() == false);
+ }
+}
+
+void MyMoneyFileTest::testSetFunctions() {
+ MyMoneyPayee user = m->user();
+
+ CPPUNIT_ASSERT(user.name().isEmpty());
+ CPPUNIT_ASSERT(user.address().isEmpty());
+ CPPUNIT_ASSERT(user.city().isEmpty());
+ CPPUNIT_ASSERT(user.state().isEmpty());
+ CPPUNIT_ASSERT(user.postcode().isEmpty());
+ CPPUNIT_ASSERT(user.telephone().isEmpty());
+ CPPUNIT_ASSERT(user.email().isEmpty());
+
+ MyMoneyFileTransaction ft;
+ storage->m_dirty = false;
+ user.setName("Name");
+ m->setUser(user);
+ CPPUNIT_ASSERT(m->dirty() == true);
+ storage->m_dirty = false;
+ user.setAddress("Street");
+ m->setUser(user);
+ CPPUNIT_ASSERT(m->dirty() == true);
+ storage->m_dirty = false;
+ user.setCity("Town");
+ m->setUser(user);
+ CPPUNIT_ASSERT(m->dirty() == true);
+ storage->m_dirty = false;
+ user.setState("County");
+ m->setUser(user);
+ CPPUNIT_ASSERT(m->dirty() == true);
+ storage->m_dirty = false;
+ user.setPostcode("Postcode");
+ m->setUser(user);
+ CPPUNIT_ASSERT(m->dirty() == true);
+ storage->m_dirty = false;
+ user.setTelephone("Telephone");
+ m->setUser(user);
+ CPPUNIT_ASSERT(m->dirty() == true);
+ storage->m_dirty = false;
+ user.setEmail("Email");
+ m->setUser(user);
+ CPPUNIT_ASSERT(m->dirty() == true);
+ storage->m_dirty = false;
+
+ ft.commit();
+ user = m->user();
+ CPPUNIT_ASSERT(user.name() == "Name");
+ CPPUNIT_ASSERT(user.address() == "Street");
+ CPPUNIT_ASSERT(user.city() == "Town");
+ CPPUNIT_ASSERT(user.state() == "County");
+ CPPUNIT_ASSERT(user.postcode() == "Postcode");
+ CPPUNIT_ASSERT(user.telephone() == "Telephone");
+ CPPUNIT_ASSERT(user.email() == "Email");
+}
+
+void MyMoneyFileTest::testAddAccounts() {
+ testAddTwoInstitutions();
+ MyMoneyAccount a, b, c;
+ a.setAccountType(MyMoneyAccount::Checkings);
+ b.setAccountType(MyMoneyAccount::Checkings);
+
+ MyMoneyInstitution institution;
+
+ storage->m_dirty = false;
+
+ CPPUNIT_ASSERT(m->accountCount() == 5);
+
+ institution = m->institution("I000001");
+ CPPUNIT_ASSERT(institution.id() == "I000001");
+
+ a.setName("Account1");
+ a.setInstitutionId(institution.id());
+
+ MyMoneyFileTransaction ft;
+ try {
+ MyMoneyAccount parent = m->asset();
+ m->addAccount(a, parent);
+ ft.commit();
+ CPPUNIT_ASSERT(m->accountCount() == 6);
+ CPPUNIT_ASSERT(a.parentAccountId() == "AStd::Asset");
+ CPPUNIT_ASSERT(a.id() == "A000001");
+ CPPUNIT_ASSERT(a.institutionId() == "I000001");
+ CPPUNIT_ASSERT(m->dirty() == true);
+ CPPUNIT_ASSERT(m->asset().accountList().count() == 1);
+ CPPUNIT_ASSERT(m->asset().accountList()[0] == "A000001");
+
+ institution = m->institution("I000001");
+ CPPUNIT_ASSERT(institution.accountCount() == 1);
+ CPPUNIT_ASSERT(institution.accountList()[0] == "A000001");
+
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+
+ // try to add this account again, should not work
+ ft.restart();
+ try {
+ MyMoneyAccount parent = m->asset();
+ m->addAccount(a, parent);
+ CPPUNIT_FAIL("Expecting exception!");
+ } catch(MyMoneyException *e) {
+ ft.commit();
+ delete e;
+ }
+
+ // check that we can modify the local object and
+ // reload it from the file
+ a.setName("AccountX");
+ a = m->account("A000001");
+ CPPUNIT_ASSERT(a.name() == "Account1");
+
+ storage->m_dirty = false;
+
+ // check if we can get the same info to a different object
+ c = m->account("A000001");
+ CPPUNIT_ASSERT(c.accountType() == MyMoneyAccount::Checkings);
+ CPPUNIT_ASSERT(c.id() == "A000001");
+ CPPUNIT_ASSERT(c.name() == "Account1");
+ CPPUNIT_ASSERT(c.institutionId() == "I000001");
+
+ CPPUNIT_ASSERT(m->dirty() == false);
+
+ // add a second account
+ institution = m->institution("I000002");
+ b.setName("Account2");
+ b.setInstitutionId(institution.id());
+ ft.restart();
+ try {
+ MyMoneyAccount parent = m->asset();
+ m->addAccount(b, parent);
+ ft.commit();
+ CPPUNIT_ASSERT(m->dirty() == true);
+ CPPUNIT_ASSERT(b.id() == "A000002");
+ CPPUNIT_ASSERT(b.parentAccountId() == "AStd::Asset");
+ CPPUNIT_ASSERT(m->accountCount() == 7);
+
+ institution = m->institution("I000001");
+ CPPUNIT_ASSERT(institution.accountCount() == 1);
+ CPPUNIT_ASSERT(institution.accountList()[0] == "A000001");
+
+ institution = m->institution("I000002");
+ CPPUNIT_ASSERT(institution.accountCount() == 1);
+ CPPUNIT_ASSERT(institution.accountList()[0] == "A000002");
+
+ CPPUNIT_ASSERT(m->asset().accountList().count() == 2);
+ CPPUNIT_ASSERT(m->asset().accountList()[0] == "A000001");
+ CPPUNIT_ASSERT(m->asset().accountList()[1] == "A000002");
+
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+
+ MyMoneyAccount p;
+
+ p = m->account("A000002");
+ CPPUNIT_ASSERT(p.accountType() == MyMoneyAccount::Checkings);
+ CPPUNIT_ASSERT(p.id() == "A000002");
+ CPPUNIT_ASSERT(p.name() == "Account2");
+ CPPUNIT_ASSERT(p.institutionId() == "I000002");
+}
+
+void MyMoneyFileTest::testModifyAccount() {
+ testAddAccounts();
+ storage->m_dirty = false;
+
+ MyMoneyAccount p = m->account("A000001");
+ MyMoneyInstitution institution;
+
+ CPPUNIT_ASSERT(p.accountType() == MyMoneyAccount::Checkings);
+ CPPUNIT_ASSERT(p.name() == "Account1");
+
+ p.setName("New account name");
+ MyMoneyFileTransaction ft;
+ try {
+ m->modifyAccount(p);
+ ft.commit();
+
+ CPPUNIT_ASSERT(m->dirty() == true);
+ CPPUNIT_ASSERT(m->accountCount() == 7);
+ CPPUNIT_ASSERT(p.accountType() == MyMoneyAccount::Checkings);
+ CPPUNIT_ASSERT(p.name() == "New account name");
+
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+ storage->m_dirty = false;
+
+ // try to move account to new institution
+ p.setInstitutionId("I000002");
+ ft.restart();
+ try {
+ m->modifyAccount(p);
+ ft.commit();
+
+ CPPUNIT_ASSERT(m->dirty() == true);
+ CPPUNIT_ASSERT(m->accountCount() == 7);
+ CPPUNIT_ASSERT(p.accountType() == MyMoneyAccount::Checkings);
+ CPPUNIT_ASSERT(p.name() == "New account name");
+ CPPUNIT_ASSERT(p.institutionId() == "I000002");
+
+ institution = m->institution("I000001");
+ CPPUNIT_ASSERT(institution.accountCount() == 0);
+
+ institution = m->institution("I000002");
+ CPPUNIT_ASSERT(institution.accountCount() == 2);
+ CPPUNIT_ASSERT(institution.accountList()[0] == "A000002");
+ CPPUNIT_ASSERT(institution.accountList()[1] == "A000001");
+
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+ storage->m_dirty = false;
+
+ // try to fool engine a bit
+ p.setParentAccountId("A000001");
+ ft.restart();
+ try {
+ m->modifyAccount(p);
+ CPPUNIT_FAIL("Expecting exception!");
+ } catch(MyMoneyException *e) {
+ ft.commit();
+ delete e;
+ }
+}
+
+void MyMoneyFileTest::testReparentAccount() {
+ testAddAccounts();
+ storage->m_dirty = false;
+
+ MyMoneyAccount p = m->account("A000001");
+ MyMoneyAccount q = m->account("A000002");
+ MyMoneyAccount o = m->account(p.parentAccountId());
+
+ // make A000001 a child of A000002
+ MyMoneyFileTransaction ft;
+ try {
+ CPPUNIT_ASSERT(p.parentAccountId() != q.id());
+ CPPUNIT_ASSERT(o.accountCount() == 2);
+ CPPUNIT_ASSERT(q.accountCount() == 0);
+ m->reparentAccount(p, q);
+ ft.commit();
+ CPPUNIT_ASSERT(m->dirty() == true);
+ CPPUNIT_ASSERT(p.parentAccountId() == q.id());
+ CPPUNIT_ASSERT(q.accountCount() == 1);
+ CPPUNIT_ASSERT(q.id() == "A000002");
+ CPPUNIT_ASSERT(p.id() == "A000001");
+ CPPUNIT_ASSERT(q.accountList()[0] == p.id());
+
+ o = m->account(o.id());
+ CPPUNIT_ASSERT(o.accountCount() == 1);
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+}
+
+void MyMoneyFileTest::testRemoveStdAccount(const MyMoneyAccount& acc) {
+ QString txt("Exception expected while removing account ");
+ txt += acc.id();
+ MyMoneyFileTransaction ft;
+ try {
+ m->removeAccount(acc);
+ CPPUNIT_FAIL(txt.latin1());
+ } catch(MyMoneyException *e) {
+ ft.commit();
+ delete e;
+ }
+}
+
+void MyMoneyFileTest::testRemoveAccount() {
+ MyMoneyInstitution institution;
+
+ testAddAccounts();
+ CPPUNIT_ASSERT(m->accountCount() == 7);
+ storage->m_dirty = false;
+
+ QString id;
+ MyMoneyAccount p = m->account("A000001");
+
+ MyMoneyFileTransaction ft;
+ try {
+ MyMoneyAccount q("Ainvalid", p);
+ m->removeAccount(q);
+ CPPUNIT_FAIL("Exception expected!");
+ } catch(MyMoneyException *e) {
+ ft.commit();
+ delete e;
+ }
+
+ ft.restart();
+ try {
+ m->removeAccount(p);
+ ft.commit();
+ CPPUNIT_ASSERT(m->dirty() == true);
+ CPPUNIT_ASSERT(m->accountCount() == 6);
+ institution = m->institution("I000001");
+ CPPUNIT_ASSERT(institution.accountCount() == 0);
+ CPPUNIT_ASSERT(m->asset().accountList().count() == 1);
+
+ institution = m->institution("I000002");
+ CPPUNIT_ASSERT(institution.accountCount() == 1);
+
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+
+ // Check that the standard account-groups cannot be removed
+ testRemoveStdAccount(m->liability());
+ testRemoveStdAccount(m->asset());
+ testRemoveStdAccount(m->expense());
+ testRemoveStdAccount(m->income());
+}
+
+void MyMoneyFileTest::testRemoveAccountTree() {
+ testReparentAccount();
+ MyMoneyAccount a = m->account("A000002");
+
+ MyMoneyFileTransaction ft;
+ // remove the account
+ try {
+ m->removeAccount(a);
+ ft.commit();
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+ CPPUNIT_ASSERT(m->accountCount() == 6);
+
+ // make sure it's gone
+ try {
+ m->account("A000002");
+ CPPUNIT_FAIL("Exception expected!");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+
+ // make sure that children are re-parented to parent account
+ try {
+ a = m->account("A000001");
+ CPPUNIT_ASSERT(a.parentAccountId() == m->asset().id());
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+
+}
+
+void MyMoneyFileTest::testAccountListRetrieval () {
+ QValueList<MyMoneyAccount> list;
+
+ storage->m_dirty = false;
+ m->accountList(list);
+ CPPUNIT_ASSERT(m->dirty() == false);
+ CPPUNIT_ASSERT(list.count() == 0);
+
+ testAddAccounts();
+
+ storage->m_dirty = false;
+ list.clear();
+ m->accountList(list);
+ CPPUNIT_ASSERT(m->dirty() == false);
+ CPPUNIT_ASSERT(list.count() == 2);
+
+ CPPUNIT_ASSERT(list[0].accountType() == MyMoneyAccount::Checkings);
+ CPPUNIT_ASSERT(list[1].accountType() == MyMoneyAccount::Checkings);
+}
+
+void MyMoneyFileTest::testAddTransaction () {
+ testAddAccounts();
+ MyMoneyTransaction t, p;
+
+ MyMoneyAccount exp1;
+ exp1.setAccountType(MyMoneyAccount::Expense);
+ exp1.setName("Expense1");
+ MyMoneyAccount exp2;
+ exp2.setAccountType(MyMoneyAccount::Expense);
+ exp2.setName("Expense2");
+
+ MyMoneyFileTransaction ft;
+ try {
+ MyMoneyAccount parent = m->expense();
+ m->addAccount(exp1, parent);
+ m->addAccount(exp2, parent);
+ ft.commit();
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+
+ // fake the last modified flag to check that the
+ // date is updated when we add the transaction
+ MyMoneyAccount a = m->account("A000001");
+ a.setLastModified(QDate(1,2,3));
+ ft.restart();
+ try {
+ m->modifyAccount(a);
+ ft.commit();
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+ ft.restart();
+
+ CPPUNIT_ASSERT(m->accountCount() == 9);
+ a = m->account("A000001");
+ CPPUNIT_ASSERT(a.lastModified() == QDate(1,2,3));
+
+ // construct a transaction and add it to the pool
+ t.setPostDate(QDate(2002,2,1));
+ t.setMemo("Memotext");
+
+ MyMoneySplit split1;
+ MyMoneySplit split2;
+
+ split1.setAccountId("A000001");
+ split1.setShares(-1000);
+ split1.setValue(-1000);
+ split2.setAccountId("A000003");
+ split2.setValue(1000);
+ split2.setShares(1000);
+ try {
+ t.addSplit(split1);
+ t.addSplit(split2);
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+
+/*
+ // FIXME: we don't have a payee and a number field right now
+ // guess we should have a number field per split, don't know
+ // about the payee
+ t.setMethod(MyMoneyCheckingTransaction::Withdrawal);
+ t.setPayee("Thomas Baumgart");
+ t.setNumber("1234");
+ t.setState(MyMoneyCheckingTransaction::Cleared);
+*/
+ storage->m_dirty = false;
+
+ ft.restart();
+ try {
+ m->addTransaction(t);
+ ft.commit();
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+ ft.restart();
+
+ CPPUNIT_ASSERT(t.id() == "T000000000000000001");
+ CPPUNIT_ASSERT(t.postDate() == QDate(2002,2,1));
+ CPPUNIT_ASSERT(t.entryDate() == QDate::currentDate());
+ CPPUNIT_ASSERT(m->dirty() == true);
+
+ // check the balance of the accounts
+ a = m->account("A000001");
+ CPPUNIT_ASSERT(a.lastModified() == QDate::currentDate());
+ CPPUNIT_ASSERT(a.balance() == MyMoneyMoney(-1000));
+
+ MyMoneyAccount b = m->account("A000003");
+ CPPUNIT_ASSERT(b.lastModified() == QDate::currentDate());
+ CPPUNIT_ASSERT(b.balance() == MyMoneyMoney(1000));
+
+ storage->m_dirty = false;
+
+ // locate transaction in MyMoneyFile via id
+
+ try {
+ p = m->transaction("T000000000000000001");
+ CPPUNIT_ASSERT(p.splitCount() == 2);
+ CPPUNIT_ASSERT(p.memo() == "Memotext");
+ CPPUNIT_ASSERT(p.splits()[0].accountId() == "A000001");
+ CPPUNIT_ASSERT(p.splits()[1].accountId() == "A000003");
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+
+ // check if it's in the account(s) as well
+
+ try {
+ p = m->transaction("A000001", 0);
+ CPPUNIT_ASSERT(p.id() == "T000000000000000001");
+ CPPUNIT_ASSERT(p.splitCount() == 2);
+ CPPUNIT_ASSERT(p.memo() == "Memotext");
+ CPPUNIT_ASSERT(p.splits()[0].accountId() == "A000001");
+ CPPUNIT_ASSERT(p.splits()[1].accountId() == "A000003");
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+
+ try {
+ p = m->transaction("A000003", 0);
+ CPPUNIT_ASSERT(p.id() == "T000000000000000001");
+ CPPUNIT_ASSERT(p.splitCount() == 2);
+ CPPUNIT_ASSERT(p.memo() == "Memotext");
+ CPPUNIT_ASSERT(p.splits()[0].accountId() == "A000001");
+ CPPUNIT_ASSERT(p.splits()[1].accountId() == "A000003");
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+}
+
+void MyMoneyFileTest::testIsStandardAccount() {
+ CPPUNIT_ASSERT(m->isStandardAccount(m->liability().id()) == true);
+ CPPUNIT_ASSERT(m->isStandardAccount(m->asset().id()) == true);
+ CPPUNIT_ASSERT(m->isStandardAccount(m->expense().id()) == true);
+ CPPUNIT_ASSERT(m->isStandardAccount(m->income().id()) == true);
+ CPPUNIT_ASSERT(m->isStandardAccount("A00001") == false);
+}
+
+void MyMoneyFileTest::testHasActiveSplits() {
+ testAddTransaction();
+
+ CPPUNIT_ASSERT(m->hasActiveSplits("A000001") == true);
+ CPPUNIT_ASSERT(m->hasActiveSplits("A000002") == false);
+}
+
+void MyMoneyFileTest::testModifyTransactionSimple() {
+ // this will test that we can modify the basic attributes
+ // of a transaction
+ testAddTransaction();
+
+ MyMoneyTransaction t = m->transaction("T000000000000000001");
+ t.setMemo("New Memotext");
+ storage->m_dirty = false;
+
+ MyMoneyFileTransaction ft;
+ try {
+ m->modifyTransaction(t);
+ ft.commit();
+ t = m->transaction("T000000000000000001");
+ CPPUNIT_ASSERT(t.memo() == "New Memotext");
+ CPPUNIT_ASSERT(m->dirty() == true);
+
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+}
+
+void MyMoneyFileTest::testModifyTransactionNewPostDate() {
+ // this will test that we can modify the basic attributes
+ // of a transaction
+ testAddTransaction();
+
+ MyMoneyTransaction t = m->transaction("T000000000000000001");
+ t.setPostDate(QDate(2004,2,1));
+ storage->m_dirty = false;
+
+ MyMoneyFileTransaction ft;
+ try {
+ m->modifyTransaction(t);
+ ft.commit();
+ t = m->transaction("T000000000000000001");
+ CPPUNIT_ASSERT(t.postDate() == QDate(2004,2,1));
+ t = m->transaction("A000001", 0);
+ CPPUNIT_ASSERT(t.id() == "T000000000000000001");
+ CPPUNIT_ASSERT(m->dirty() == true);
+
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+}
+
+void MyMoneyFileTest::testModifyTransactionNewAccount() {
+ // this will test that we can modify the basic attributes
+ // of a transaction
+ testAddTransaction();
+
+ MyMoneyTransaction t = m->transaction("T000000000000000001");
+ MyMoneySplit s;
+ s = t.splits()[0];
+ s.setAccountId("A000002");
+ t.modifySplit(s);
+
+ storage->m_dirty = false;
+ MyMoneyFileTransaction ft;
+ try {
+/* removed with MyMoneyAccount::Transaction
+ CPPUNIT_ASSERT(m->account("A000001").transactionCount() == 1);
+ CPPUNIT_ASSERT(m->account("A000002").transactionCount() == 0);
+ CPPUNIT_ASSERT(m->account("A000003").transactionCount() == 1);
+*/
+ MyMoneyTransactionFilter f1("A000001");
+ MyMoneyTransactionFilter f2("A000002");
+ MyMoneyTransactionFilter f3("A000003");
+ CPPUNIT_ASSERT(m->transactionList(f1).count() == 1);
+ CPPUNIT_ASSERT(m->transactionList(f2).count() == 0);
+ CPPUNIT_ASSERT(m->transactionList(f3).count() == 1);
+
+ m->modifyTransaction(t);
+ ft.commit();
+ t = m->transaction("T000000000000000001");
+ CPPUNIT_ASSERT(t.postDate() == QDate(2002,2,1));
+ t = m->transaction("A000002", 0);
+ CPPUNIT_ASSERT(m->dirty() == true);
+/* removed with MyMoneyAccount::Transaction
+ CPPUNIT_ASSERT(m->account("A000001").transactionCount() == 0);
+ CPPUNIT_ASSERT(m->account("A000002").transactionCount() == 1);
+ CPPUNIT_ASSERT(m->account("A000003").transactionCount() == 1);
+*/
+ CPPUNIT_ASSERT(m->transactionList(f1).count() == 0);
+ CPPUNIT_ASSERT(m->transactionList(f2).count() == 1);
+ CPPUNIT_ASSERT(m->transactionList(f3).count() == 1);
+
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+}
+
+void MyMoneyFileTest::testRemoveTransaction () {
+ testModifyTransactionNewPostDate();
+
+ MyMoneyTransaction t;
+ t = m->transaction("T000000000000000001");
+
+ storage->m_dirty = false;
+ MyMoneyFileTransaction ft;
+ try {
+ m->removeTransaction(t);
+ ft.commit();
+ CPPUNIT_ASSERT(m->dirty() == true);
+ CPPUNIT_ASSERT(m->transactionCount() == 0);
+/* removed with MyMoneyAccount::Transaction
+ CPPUNIT_ASSERT(m->account("A000001").transactionCount() == 0);
+ CPPUNIT_ASSERT(m->account("A000002").transactionCount() == 0);
+ CPPUNIT_ASSERT(m->account("A000003").transactionCount() == 0);
+*/
+ MyMoneyTransactionFilter f1("A000001");
+ MyMoneyTransactionFilter f2("A000002");
+ MyMoneyTransactionFilter f3("A000003");
+ CPPUNIT_ASSERT(m->transactionList(f1).count() == 0);
+ CPPUNIT_ASSERT(m->transactionList(f2).count() == 0);
+ CPPUNIT_ASSERT(m->transactionList(f3).count() == 0);
+
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+}
+
+/*
+ * This function is currently not implemented. It's kind of tricky
+ * because it modifies a lot of objects in a single call. This might
+ * be a problem for the undo/redo stuff. That's why I left it out in
+ * the first run. We migh add it, if we need it.
+ * /
+void testMoveSplits() {
+ testModifyTransactionNewPostDate();
+
+ CPPUNIT_ASSERT(m->account("A000001").transactionCount() == 1);
+ CPPUNIT_ASSERT(m->account("A000002").transactionCount() == 0);
+ CPPUNIT_ASSERT(m->account("A000003").transactionCount() == 1);
+
+ try {
+ m->moveSplits("A000001", "A000002");
+ CPPUNIT_ASSERT(m->account("A000001").transactionCount() == 0);
+ CPPUNIT_ASSERT(m->account("A000002").transactionCount() == 1);
+ CPPUNIT_ASSERT(m->account("A000003").transactionCount() == 1);
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+}
+*/
+
+void MyMoneyFileTest::testBalanceTotal() {
+ testAddTransaction();
+ MyMoneyTransaction t;
+
+ // construct a transaction and add it to the pool
+ t.setPostDate(QDate(2002,2,1));
+ t.setMemo("Memotext");
+
+ MyMoneySplit split1;
+ MyMoneySplit split2;
+
+ MyMoneyFileTransaction ft;
+ try {
+ split1.setAccountId("A000002");
+ split1.setShares(-1000);
+ split1.setValue(-1000);
+ split2.setAccountId("A000004");
+ split2.setValue(1000);
+ split2.setShares(1000);
+ t.addSplit(split1);
+ t.addSplit(split2);
+ m->addTransaction(t);
+ ft.commit();
+ ft.restart();
+ CPPUNIT_ASSERT(t.id() == "T000000000000000002");
+ CPPUNIT_ASSERT(m->totalBalance("A000001") == MyMoneyMoney(-1000));
+ CPPUNIT_ASSERT(m->totalBalance("A000002") == MyMoneyMoney(-1000));
+
+ MyMoneyAccount p = m->account("A000001");
+ MyMoneyAccount q = m->account("A000002");
+ m->reparentAccount(p, q);
+ ft.commit();
+ CPPUNIT_ASSERT(m->totalBalance("A000001") == MyMoneyMoney(-1000));
+ CPPUNIT_ASSERT(m->totalBalance("A000002") == MyMoneyMoney(-2000));
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+}
+
+void MyMoneyFileTest::testSetAccountName() {
+ MyMoneyFileTransaction ft;
+ try {
+ m->setAccountName(STD_ACC_LIABILITY, "Verbindlichkeiten");
+ ft.commit();
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+ ft.restart();
+ try {
+ m->setAccountName(STD_ACC_ASSET, "Vermögen");
+ ft.commit();
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+ ft.restart();
+ try {
+ m->setAccountName(STD_ACC_EXPENSE, "Ausgaben");
+ ft.commit();
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+ ft.restart();
+ try {
+ m->setAccountName(STD_ACC_INCOME, "Einnahmen");
+ ft.commit();
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+ ft.restart();
+
+ CPPUNIT_ASSERT(m->liability().name() == "Verbindlichkeiten");
+ CPPUNIT_ASSERT(m->asset().name() == "Vermögen");
+ CPPUNIT_ASSERT(m->expense().name() == "Ausgaben");
+ CPPUNIT_ASSERT(m->income().name() == "Einnahmen");
+
+ try {
+ m->setAccountName("A000001", "New account name");
+ ft.commit();
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+}
+
+void MyMoneyFileTest::testAddPayee() {
+ MyMoneyPayee p;
+
+ p.setName("THB");
+ CPPUNIT_ASSERT(m->dirty() == false);
+ MyMoneyFileTransaction ft;
+ try {
+ m->addPayee(p);
+ ft.commit();
+ CPPUNIT_ASSERT(m->dirty() == true);
+ CPPUNIT_ASSERT(p.id() == "P000001");
+
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneyFileTest::testModifyPayee() {
+ MyMoneyPayee p;
+
+ testAddPayee();
+
+ p = m->payee("P000001");
+ p.setName("New name");
+ MyMoneyFileTransaction ft;
+ try {
+ m->modifyPayee(p);
+ ft.commit();
+ p = m->payee("P000001");
+ CPPUNIT_ASSERT(p.name() == "New name");
+
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneyFileTest::testRemovePayee() {
+ MyMoneyPayee p;
+
+ testAddPayee();
+ CPPUNIT_ASSERT(m->payeeList().count() == 1);
+
+ p = m->payee("P000001");
+ MyMoneyFileTransaction ft;
+ try {
+ m->removePayee(p);
+ ft.commit();
+ CPPUNIT_ASSERT(m->payeeList().count() == 0);
+
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneyFileTest::testAddTransactionStd() {
+ testAddAccounts();
+ MyMoneyTransaction t, p;
+ MyMoneyAccount a;
+
+ a = m->account("A000001");
+
+ // construct a transaction and add it to the pool
+ t.setPostDate(QDate(2002,2,1));
+ t.setMemo("Memotext");
+
+ MyMoneySplit split1;
+ MyMoneySplit split2;
+
+ split1.setAccountId("A000001");
+ split1.setShares(-1000);
+ split1.setValue(-1000);
+ split2.setAccountId(STD_ACC_EXPENSE);
+ split2.setValue(1000);
+ split2.setShares(1000);
+ try {
+ t.addSplit(split1);
+ t.addSplit(split2);
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+
+/*
+ // FIXME: we don't have a payee and a number field right now
+ // guess we should have a number field per split, don't know
+ // about the payee
+ t.setMethod(MyMoneyCheckingTransaction::Withdrawal);
+ t.setPayee("Thomas Baumgart");
+ t.setNumber("1234");
+ t.setState(MyMoneyCheckingTransaction::Cleared);
+*/
+ storage->m_dirty = false;
+
+ MyMoneyFileTransaction ft;
+ try {
+ m->addTransaction(t);
+ ft.commit();
+ CPPUNIT_FAIL("Missing expected exception!");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+
+ CPPUNIT_ASSERT(m->dirty() == false);
+}
+
+void MyMoneyFileTest::testAttachStorage() {
+ IMyMoneyStorage *store = new MyMoneySeqAccessMgr;
+ MyMoneyFile *file = new MyMoneyFile;
+
+ CPPUNIT_ASSERT(file->storageAttached() == false);
+ try {
+ file->attachStorage(store);
+ CPPUNIT_ASSERT(file->storageAttached() == true);
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+
+ try {
+ file->attachStorage(store);
+ CPPUNIT_FAIL("Exception expected!");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+
+ try {
+ file->attachStorage(0);
+ CPPUNIT_FAIL("Exception expected!");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+
+ try {
+ file->detachStorage(store);
+ CPPUNIT_ASSERT(file->storageAttached() == false);
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception!");
+ }
+
+ delete store;
+ delete file;
+}
+
+
+void MyMoneyFileTest::testAccount2Category() {
+ testReparentAccount();
+ CPPUNIT_ASSERT(m->accountToCategory("A000001") == "Account2:Account1");
+ CPPUNIT_ASSERT(m->accountToCategory("A000002") == "Account2");
+}
+
+void MyMoneyFileTest::testCategory2Account() {
+ testAddTransaction();
+ MyMoneyAccount a = m->account("A000003");
+ MyMoneyAccount b = m->account("A000004");
+
+ MyMoneyFileTransaction ft;
+ try {
+ m->reparentAccount(b, a);
+ ft.commit();
+ CPPUNIT_ASSERT(m->categoryToAccount("Expense1") == "A000003");
+ CPPUNIT_ASSERT(m->categoryToAccount("Expense1:Expense2") == "A000004");
+ CPPUNIT_ASSERT(m->categoryToAccount("Acc2").isEmpty());
+ } catch(MyMoneyException *e) {
+ unexpectedException(e);
+ }
+}
+
+void MyMoneyFileTest::testAttachedStorage() {
+ CPPUNIT_ASSERT(m->storageAttached() == true);
+ CPPUNIT_ASSERT(m->storage() != 0);
+ IMyMoneyStorage *p = m->storage();
+ m->detachStorage(p);
+ CPPUNIT_ASSERT(m->storageAttached() == false);
+ CPPUNIT_ASSERT(m->storage() == 0);
+ m->attachStorage(p);
+ CPPUNIT_ASSERT(m->storageAttached() == true);
+ CPPUNIT_ASSERT(m->storage() != 0);
+}
+
+void MyMoneyFileTest::testHasAccount() {
+ testAddAccounts();
+
+ MyMoneyAccount a, b;
+ a.setAccountType(MyMoneyAccount::Checkings);
+ a.setName("Account3");
+ b = m->account("A000001");
+ MyMoneyFileTransaction ft;
+ try {
+ m->addAccount(a, b);
+ ft.commit();
+ CPPUNIT_ASSERT(m->accountCount() == 8);
+ CPPUNIT_ASSERT(a.parentAccountId() == "A000001");
+ CPPUNIT_ASSERT(m->hasAccount("A000001", "Account3") == true);
+ CPPUNIT_ASSERT(m->hasAccount("A000001", "Account2") == false);
+ CPPUNIT_ASSERT(m->hasAccount("A000002", "Account3") == false);
+ } catch(MyMoneyException *e) {
+ unexpectedException(e);
+ }
+}
+
+void MyMoneyFileTest::testAddEquityAccount() {
+ MyMoneyAccount i;
+ i.setName("Investment");
+ i.setAccountType(MyMoneyAccount::Investment);
+
+ MyMoneyFileTransaction ft;
+ try {
+ MyMoneyAccount parent = m->asset();
+ m->addAccount(i, parent);
+ ft.commit();
+ } catch(MyMoneyException *e) {
+ unexpectedException(e);
+ }
+ // keep a copy for later use
+ m_inv = i;
+
+ // make sure, that only equity accounts can be children to it
+ MyMoneyAccount a;
+ a.setName("Testaccount");
+ QValueList<MyMoneyAccount::accountTypeE> list;
+ list << MyMoneyAccount::Checkings;
+ list << MyMoneyAccount::Savings;
+ list << MyMoneyAccount::Cash;
+ list << MyMoneyAccount::CreditCard;
+ list << MyMoneyAccount::Loan;
+ list << MyMoneyAccount::CertificateDep;
+ list << MyMoneyAccount::Investment;
+ list << MyMoneyAccount::MoneyMarket;
+ list << MyMoneyAccount::Asset;
+ list << MyMoneyAccount::Liability;
+ list << MyMoneyAccount::Currency;
+ list << MyMoneyAccount::Income;
+ list << MyMoneyAccount::Expense;
+ list << MyMoneyAccount::AssetLoan;
+
+ QValueList<MyMoneyAccount::accountTypeE>::Iterator it;
+ for(it = list.begin(); it != list.end(); ++it) {
+ a.setAccountType(*it);
+ ft.restart();
+ try {
+ char msg[100];
+ m->addAccount(a, i);
+ sprintf(msg, "Can add non-equity type %d to investment", *it);
+ CPPUNIT_FAIL(msg);
+ } catch(MyMoneyException *e) {
+ ft.commit();
+ delete e;
+ }
+ }
+ ft.restart();
+ try {
+ a.setName("Teststock");
+ a.setAccountType(MyMoneyAccount::Stock);
+ m->addAccount(a,i);
+ ft.commit();
+ } catch(MyMoneyException *e) {
+ unexpectedException(e);
+ }
+}
+
+void MyMoneyFileTest::testReparentEquity() {
+ testAddEquityAccount();
+ testAddEquityAccount();
+ MyMoneyAccount parent;
+
+ // check the bad cases
+ QValueList<MyMoneyAccount::accountTypeE> list;
+ list << MyMoneyAccount::Checkings;
+ list << MyMoneyAccount::Savings;
+ list << MyMoneyAccount::Cash;
+ list << MyMoneyAccount::CertificateDep;
+ list << MyMoneyAccount::MoneyMarket;
+ list << MyMoneyAccount::Asset;
+ list << MyMoneyAccount::AssetLoan;
+ list << MyMoneyAccount::Currency;
+ parent = m->asset();
+ testReparentEquity(list, parent);
+
+ list.clear();
+ list << MyMoneyAccount::CreditCard;
+ list << MyMoneyAccount::Loan;
+ list << MyMoneyAccount::Liability;
+ parent = m->liability();
+ testReparentEquity(list, parent);
+
+ list.clear();
+ list << MyMoneyAccount::Income;
+ parent = m->income();
+ testReparentEquity(list, parent);
+
+ list.clear();
+ list << MyMoneyAccount::Expense;
+ parent = m->expense();
+ testReparentEquity(list, parent);
+
+ // now check the good case
+ MyMoneyAccount stock = m->account("A000002");
+ MyMoneyAccount inv = m->account(m_inv.id());
+ MyMoneyFileTransaction ft;
+ try {
+ m->reparentAccount(stock, inv);
+ ft.commit();
+ } catch(MyMoneyException *e) {
+ unexpectedException(e);
+ }
+}
+
+void MyMoneyFileTest::testReparentEquity(QValueList<MyMoneyAccount::accountTypeE>& list, MyMoneyAccount& parent)
+{
+ MyMoneyAccount a;
+ MyMoneyAccount stock = m->account("A000002");
+
+ QValueList<MyMoneyAccount::accountTypeE>::Iterator it;
+ MyMoneyFileTransaction ft;
+ for(it = list.begin(); it != list.end(); ++it) {
+ a.setName(QString("Testaccount %1").arg(*it));
+ a.setAccountType(*it);
+ try {
+ m->addAccount(a, parent);
+ char msg[100];
+ m->reparentAccount(stock, a);
+ sprintf(msg, "Can reparent stock to non-investment type %d account", *it);
+ CPPUNIT_FAIL(msg);
+ } catch(MyMoneyException *e) {
+ ft.commit();
+ delete e;
+ }
+ ft.restart();
+ }
+}
+
+void MyMoneyFileTest::testBaseCurrency(void)
+{
+ MyMoneySecurity base("EUR", "Euro", QChar(0x20ac));
+ MyMoneySecurity ref;
+
+ // make sure, no base currency is set
+ try {
+ ref = m->baseCurrency();
+ CPPUNIT_ASSERT(ref.id().isEmpty());
+ } catch(MyMoneyException *e) {
+ unexpectedException(e);
+ }
+
+ // make sure, we cannot assign an unknown currency
+ try {
+ m->setBaseCurrency(base);
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+
+ MyMoneyFileTransaction ft;
+ // add the currency and try again
+ try {
+ m->addCurrency(base);
+ m->setBaseCurrency(base);
+ ft.commit();
+ } catch(MyMoneyException *e) {
+ unexpectedException(e);
+ }
+ ft.restart();
+
+ // make sure, the base currency is set
+ try {
+ ref = m->baseCurrency();
+ CPPUNIT_ASSERT(ref.id() == "EUR");
+ CPPUNIT_ASSERT(ref.name() == "Euro");
+ CPPUNIT_ASSERT(ref.tradingSymbol() == QChar(0x20ac));
+ } catch(MyMoneyException *e) {
+ unexpectedException(e);
+ }
+
+ // check if it gets reset when attaching a new storage
+ m->detachStorage(storage);
+
+ MyMoneySeqAccessMgr* newStorage = new MyMoneySeqAccessMgr;
+ m->attachStorage(newStorage);
+
+ ref = m->baseCurrency();
+ CPPUNIT_ASSERT(ref.id().isEmpty());
+
+ m->detachStorage(newStorage);
+ delete newStorage;
+
+ m->attachStorage(storage);
+ ref = m->baseCurrency();
+ CPPUNIT_ASSERT(ref.id() == "EUR");
+ CPPUNIT_ASSERT(ref.name() == "Euro");
+ CPPUNIT_ASSERT(ref.tradingSymbol() == QChar(0x20ac));
+}
+
+void MyMoneyFileTest::testOpeningBalanceNoBase(void)
+{
+ MyMoneyAccount openingAcc;
+ MyMoneySecurity base;
+
+ try {
+ base = m->baseCurrency();
+ openingAcc = m->openingBalanceAccount(base);
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+}
+
+void MyMoneyFileTest::testOpeningBalance(void)
+{
+ MyMoneyAccount openingAcc;
+ MyMoneySecurity second("USD", "US Dollar", "$");
+ testBaseCurrency();
+
+ try {
+ openingAcc = m->openingBalanceAccount(m->baseCurrency());
+ CPPUNIT_ASSERT(openingAcc.parentAccountId() == m->equity().id());
+ CPPUNIT_ASSERT(openingAcc.name() == MyMoneyFile::OpeningBalancesPrefix);
+ CPPUNIT_ASSERT(openingAcc.openingDate() == QDate::currentDate());
+ } catch(MyMoneyException *e) {
+ unexpectedException(e);
+ }
+
+ // add a second currency
+ MyMoneyFileTransaction ft;
+ try {
+ m->addCurrency(second);
+ ft.commit();
+ } catch(MyMoneyException *e) {
+ unexpectedException(e);
+ }
+
+ QString refName = QString("%1 (%2)").arg(MyMoneyFile::OpeningBalancesPrefix).arg("USD");
+ try {
+ openingAcc = m->openingBalanceAccount(second);
+ CPPUNIT_ASSERT(openingAcc.parentAccountId() == m->equity().id());
+ CPPUNIT_ASSERT(openingAcc.name() == refName);
+ CPPUNIT_ASSERT(openingAcc.openingDate() == QDate::currentDate());
+ } catch(MyMoneyException *e) {
+ unexpectedException(e);
+ }
+}
+
+void MyMoneyFileTest::testModifyStdAccount() {
+ CPPUNIT_ASSERT(m->asset().currencyId().isEmpty());
+ CPPUNIT_ASSERT(m->asset().name() == "Asset");
+ testBaseCurrency();
+ CPPUNIT_ASSERT(m->asset().currencyId().isEmpty());
+ CPPUNIT_ASSERT(!m->baseCurrency().id().isEmpty());
+
+ MyMoneyFileTransaction ft;
+ try {
+ MyMoneyAccount acc = m->asset();
+ acc.setName("Anlagen");
+ acc.setCurrencyId(m->baseCurrency().id());
+ m->modifyAccount(acc);
+ ft.commit();
+
+ CPPUNIT_ASSERT(m->asset().name() == "Anlagen");
+ CPPUNIT_ASSERT(m->asset().currencyId() == m->baseCurrency().id());
+ } catch(MyMoneyException *e) {
+ unexpectedException(e);
+ }
+
+ ft.restart();
+ try {
+ MyMoneyAccount acc = m->asset();
+ acc.setNumber("Test");
+ m->modifyAccount(acc);
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch(MyMoneyException *e) {
+ ft.rollback();
+ delete e;
+ }
+
+}
diff --git a/kmymoney2/mymoney/mymoneyfiletest.h b/kmymoney2/mymoney/mymoneyfiletest.h
new file mode 100644
index 0000000..b54af25
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyfiletest.h
@@ -0,0 +1,128 @@
+/***************************************************************************
+ mymoneyfiletest.h
+ -------------------
+ copyright : (C) 2002 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 __MYMONEYFILETEST_H__
+#define __MYMONEYFILETEST_H__
+
+#include <cppunit/extensions/HelperMacros.h>
+#include "autotest.h"
+
+#define private public
+#define protected public
+#include "mymoneyfile.h"
+#include "storage/mymoneyseqaccessmgr.h"
+#undef private
+#undef protected
+
+class MyMoneyFileTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(MyMoneyFileTest);
+ CPPUNIT_TEST(testEmptyConstructor);
+ CPPUNIT_TEST(testAddOneInstitution);
+ CPPUNIT_TEST(testAddTwoInstitutions);
+ CPPUNIT_TEST(testInstitutionRetrieval);
+ CPPUNIT_TEST(testRemoveInstitution);
+ CPPUNIT_TEST(testInstitutionListRetrieval);
+ CPPUNIT_TEST(testInstitutionModify);
+ CPPUNIT_TEST(testSetFunctions);
+ CPPUNIT_TEST(testAddAccounts);
+ CPPUNIT_TEST(testModifyAccount);
+ CPPUNIT_TEST(testModifyStdAccount);
+ CPPUNIT_TEST(testReparentAccount);
+ CPPUNIT_TEST(testRemoveAccount);
+ CPPUNIT_TEST(testRemoveAccountTree);
+ CPPUNIT_TEST(testAccountListRetrieval);
+ CPPUNIT_TEST(testAddTransaction);
+ CPPUNIT_TEST(testHasActiveSplits);
+ CPPUNIT_TEST(testIsStandardAccount);
+ CPPUNIT_TEST(testModifyTransactionSimple);
+ CPPUNIT_TEST(testModifyTransactionNewPostDate);
+ CPPUNIT_TEST(testModifyTransactionNewAccount);
+ CPPUNIT_TEST(testRemoveTransaction);
+ CPPUNIT_TEST(testBalanceTotal);
+ CPPUNIT_TEST(testSetAccountName);
+ CPPUNIT_TEST(testAddPayee);
+ CPPUNIT_TEST(testModifyPayee);
+ CPPUNIT_TEST(testRemovePayee);
+ CPPUNIT_TEST(testAddTransactionStd);
+ CPPUNIT_TEST(testAttachStorage);
+ CPPUNIT_TEST(testAccount2Category);
+ CPPUNIT_TEST(testCategory2Account);
+ CPPUNIT_TEST(testAttachedStorage);
+ CPPUNIT_TEST(testHasAccount);
+ CPPUNIT_TEST(testAddEquityAccount);
+ CPPUNIT_TEST(testReparentEquity);
+ CPPUNIT_TEST(testBaseCurrency);
+ CPPUNIT_TEST(testOpeningBalanceNoBase);
+ CPPUNIT_TEST(testOpeningBalance);
+#if 0
+ CPPUNIT_TEST(testMoveSplits);
+#endif
+ CPPUNIT_TEST_SUITE_END();
+protected:
+ MyMoneyFile *m;
+ MyMoneySeqAccessMgr* storage;
+ MyMoneyAccount m_inv;
+
+public:
+ MyMoneyFileTest ();
+
+ void setUp ();
+ void tearDown ();
+ void testEmptyConstructor();
+ void testAddOneInstitution();
+ void testAddTwoInstitutions();
+ void testRemoveInstitution();
+ void testInstitutionRetrieval ();
+ void testInstitutionListRetrieval ();
+ void testInstitutionModify();
+ void testSetFunctions();
+ void testAddAccounts();
+ void testModifyAccount();
+ void testModifyStdAccount();
+ void testReparentAccount();
+ void testRemoveAccount();
+ void testRemoveAccountTree();
+ void testAccountListRetrieval ();
+ void testAddTransaction ();
+ void testIsStandardAccount();
+ void testHasActiveSplits();
+ void testModifyTransactionSimple();
+ void testModifyTransactionNewPostDate();
+ void testModifyTransactionNewAccount();
+ void testRemoveTransaction ();
+ void testBalanceTotal();
+ void testSetAccountName();
+ void testAddPayee();
+ void testModifyPayee();
+ void testRemovePayee();
+ void testAddTransactionStd();
+ void testAttachStorage();
+ void testAccount2Category();
+ void testCategory2Account();
+ void testAttachedStorage();
+ void testHasAccount();
+ void testAddEquityAccount();
+ void testReparentEquity();
+ void testReparentEquity(QValueList<MyMoneyAccount::accountTypeE>& list, MyMoneyAccount& parent);
+ void testBaseCurrency();
+ void testOpeningBalanceNoBase();
+ void testOpeningBalance();
+
+private:
+ void testRemoveStdAccount(const MyMoneyAccount& acc);
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneyfinancialcalculator.cpp b/kmymoney2/mymoney/mymoneyfinancialcalculator.cpp
new file mode 100644
index 0000000..d6686a6
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyfinancialcalculator.cpp
@@ -0,0 +1,348 @@
+/***************************************************************************
+ mymoneyfinancialcalculator.cpp - description
+ -------------------
+ begin : Tue Oct 21 2003
+ copyright : (C) 2000-2003 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 <math.h>
+#include <stdio.h>
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+// ----------------------------------------------------------------------------
+// KDE Includes
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+
+#include "mymoneyfinancialcalculator.h"
+#include "mymoneyexception.h"
+
+// #ifndef HAVE_ROUND
+// #undef roundl
+// #define roundl(a) rnd(a)
+
+FCALC_DOUBLE MyMoneyFinancialCalculator::rnd(const FCALC_DOUBLE x) const
+{
+ FCALC_DOUBLE r,f;
+
+ if(m_prec > 0) {
+#ifdef HAVE_ROUND
+ f = powl(10.0, m_prec);
+ r = roundl(x * f)/f;
+#else
+ char buf[50];
+#if HAVE_LONG_DOUBLE
+ sprintf (buf, "%.*Lf", m_prec, x);
+ sscanf (buf, "%Lf", &r);
+#else
+ sprintf (buf, "%.*f", m_prec, x);
+ sscanf (buf, "%lf", &r);
+#endif
+#endif
+ } else
+ r = roundl(x);
+ return r;
+}
+// #endif
+
+static inline FCALC_DOUBLE dabs(const FCALC_DOUBLE x)
+{
+ return (x >= 0.0) ? x : -x;
+}
+
+MyMoneyFinancialCalculator::MyMoneyFinancialCalculator()
+{
+ setPrec();
+ setPF();
+ setCF();
+ setBep();
+ setDisc();
+
+ setNpp(0.0);
+ setIr(0.0);
+ setPv(0.0);
+ setPmt(0.0);
+ setFv(0.0);
+
+ // clear the mask
+ m_mask = 0;
+}
+
+MyMoneyFinancialCalculator::~MyMoneyFinancialCalculator()
+{
+}
+
+void MyMoneyFinancialCalculator::setPrec(const unsigned short prec)
+{
+ m_prec = prec;
+}
+
+void MyMoneyFinancialCalculator::setPF(const unsigned short PF)
+{
+ m_PF = PF;
+}
+
+void MyMoneyFinancialCalculator::setCF(const unsigned short CF)
+{
+ m_CF = CF;
+}
+
+void MyMoneyFinancialCalculator::setBep(const bool bep)
+{
+ m_bep = bep;
+}
+
+void MyMoneyFinancialCalculator::setDisc(const bool disc)
+{
+ m_disc = disc;
+}
+
+void MyMoneyFinancialCalculator::setIr(const FCALC_DOUBLE ir)
+{
+ m_ir = ir;
+ m_mask |= IR_SET;
+}
+
+void MyMoneyFinancialCalculator::setPv(const FCALC_DOUBLE pv)
+{
+ m_pv = pv;
+ m_mask |= PV_SET;
+}
+
+void MyMoneyFinancialCalculator::setPmt(const FCALC_DOUBLE pmt)
+{
+ m_pmt = pmt;
+ m_mask |= PMT_SET;
+}
+
+void MyMoneyFinancialCalculator::setNpp(const FCALC_DOUBLE npp)
+{
+ m_npp = npp;
+ m_mask |= NPP_SET;
+}
+
+void MyMoneyFinancialCalculator::setFv(const FCALC_DOUBLE fv)
+{
+ m_fv = fv;
+ m_mask |= FV_SET;
+}
+
+FCALC_DOUBLE MyMoneyFinancialCalculator::numPayments(void)
+{
+ const unsigned short mask = PV_SET | IR_SET | PMT_SET | FV_SET;
+
+ if((m_mask & mask) != mask)
+ throw new MYMONEYEXCEPTION("Not all parameters set for calculation of numPayments");
+
+ FCALC_DOUBLE eint = eff_int();
+ FCALC_DOUBLE CC = _Cx(eint);
+
+ CC = (CC - m_fv) / (CC + m_pv);
+ m_npp = (CC > 0.0) ? logl (CC) / logl (eint +1.0) : 0.0;
+
+ m_mask |= NPP_SET;
+ return m_npp;
+}
+
+FCALC_DOUBLE MyMoneyFinancialCalculator::payment(void)
+{
+ const unsigned short mask = PV_SET | IR_SET | NPP_SET | FV_SET;
+
+ if((m_mask & mask) != mask)
+ throw new MYMONEYEXCEPTION("Not all parameters set for calculation of payment");
+
+ FCALC_DOUBLE eint = eff_int();
+ FCALC_DOUBLE AA = _Ax(eint);
+ FCALC_DOUBLE BB = _Bx(eint);
+
+ m_pmt = -rnd((m_fv + m_pv * (AA + 1.0)) / (AA * BB));
+ //m_pmt = -floorl((m_fv + m_pv * (AA + 1.0)) / (AA * BB));
+
+ m_mask |= PMT_SET;
+ return m_pmt;
+}
+
+FCALC_DOUBLE MyMoneyFinancialCalculator::presentValue(void)
+{
+ const unsigned short mask = PMT_SET | IR_SET | NPP_SET | FV_SET;
+
+ if((m_mask & mask) != mask)
+ throw new MYMONEYEXCEPTION("Not all parameters set for calculation of payment");
+
+ FCALC_DOUBLE eint = eff_int();
+ FCALC_DOUBLE AA = _Ax(eint);
+ FCALC_DOUBLE CC = _Cx(eint);
+
+ m_pv = rnd(-(m_fv + (AA * CC)) / (AA + 1.0));
+
+ m_mask |= PV_SET;
+ return m_pv;
+}
+
+FCALC_DOUBLE MyMoneyFinancialCalculator::futureValue(void)
+{
+ const unsigned short mask = PMT_SET | IR_SET | NPP_SET | PV_SET;
+
+ if((m_mask & mask) != mask)
+ throw new MYMONEYEXCEPTION("Not all parameters set for calculation of payment");
+
+ FCALC_DOUBLE eint = eff_int();
+ FCALC_DOUBLE AA = _Ax(eint);
+ FCALC_DOUBLE CC = _Cx(eint);
+ m_fv = rnd(-(m_pv + AA * (m_pv + CC)));
+
+ m_mask |= FV_SET;
+ return m_fv;
+}
+
+FCALC_DOUBLE MyMoneyFinancialCalculator::interestRate(void)
+{
+ FCALC_DOUBLE eint = 0.0;
+ FCALC_DOUBLE a = 0.0;
+ FCALC_DOUBLE dik = 0.0;
+
+ const FCALC_DOUBLE ratio = 1e4;
+ int ri;
+
+ if (m_pmt == 0.0) {
+ eint = powl ((dabs (m_fv) / dabs (m_pv)), (1.0 / m_npp)) - 1.0;
+ } else {
+ if ((m_pmt * m_fv) < 0.0) {
+ if(m_pv)
+ a = -1.0;
+ else
+ a = 1.0;
+ eint =
+ dabs ((m_fv + a * m_npp * m_pmt) /
+ (3.0 *
+ ((m_npp - 1.0) * (m_npp - 1.0) * m_pmt + m_pv -
+ m_fv)));
+ } else {
+ if ((m_pv * m_pmt) < 0.0) {
+ eint = dabs ((m_npp * m_pmt + m_pv + m_fv) / (m_npp * m_pv));
+ } else {
+ a = dabs (m_pmt / (dabs(m_pv) + dabs(m_fv)));
+ eint = a + 1.0 / (a * m_npp * m_npp * m_npp);
+ }
+ }
+ do {
+ try {
+ dik = _fi(eint) / _fip(eint);
+ eint -= dik;
+ } catch(MyMoneyException *e) {
+ delete e;
+ eint = 0;
+ }
+ (void) modfl(ratio * (dik / eint), &a);
+ ri = static_cast<unsigned> (a);
+ }
+ while (ri);
+ }
+ m_mask |= IR_SET;
+ m_ir = rnd(nom_int(eint) * 100.0);
+ return m_ir;
+}
+
+FCALC_DOUBLE MyMoneyFinancialCalculator::_fi(const FCALC_DOUBLE eint) const
+{
+ return _Ax(eint) * (m_pv + _Cx(eint)) + m_pv + m_fv;
+}
+
+FCALC_DOUBLE MyMoneyFinancialCalculator::_fip(const FCALC_DOUBLE eint) const
+{
+ double AA = _Ax(eint);
+ double CC = _Cx(eint);
+ double D = (AA + 1.0) / (eint + 1.0);
+
+ return m_npp *(m_pv + CC) * D - (AA * CC) / eint;
+}
+
+FCALC_DOUBLE MyMoneyFinancialCalculator::_Ax(const FCALC_DOUBLE eint) const
+{
+ return powl ((eint + 1.0), m_npp) - 1.0;
+}
+
+FCALC_DOUBLE MyMoneyFinancialCalculator::_Bx(const FCALC_DOUBLE eint) const
+{
+ if(eint == 0.0)
+ throw new MYMONEYEXCEPTION("Zero interest");
+
+ if(m_bep == false)
+ return static_cast<FCALC_DOUBLE>(1.0) / eint;
+
+ return (eint + 1.0) / eint;
+}
+
+FCALC_DOUBLE MyMoneyFinancialCalculator::_Cx(const FCALC_DOUBLE eint) const
+{
+ return m_pmt * _Bx(eint);
+}
+
+FCALC_DOUBLE MyMoneyFinancialCalculator::eff_int(void) const
+{
+ FCALC_DOUBLE nint = m_ir / 100.0;
+ FCALC_DOUBLE eint;
+
+ if(m_disc) { // periodically compound?
+ if(m_CF == m_PF) { // same frequency?
+ eint = nint / static_cast<FCALC_DOUBLE>(m_CF);
+
+ } else {
+ eint = powl((static_cast<FCALC_DOUBLE>(1.0) + (nint / static_cast<FCALC_DOUBLE>(m_CF))),
+ (static_cast<FCALC_DOUBLE>(m_CF) / static_cast<FCALC_DOUBLE>(m_PF))) - 1.0;
+
+ }
+
+ } else {
+ eint = expl(nint / static_cast<FCALC_DOUBLE>(m_PF)) - 1.0;
+ }
+
+ return eint;
+}
+
+FCALC_DOUBLE MyMoneyFinancialCalculator::nom_int(const FCALC_DOUBLE eint) const
+{
+ FCALC_DOUBLE nint;
+
+ if(m_disc) {
+ if(m_CF == m_PF) {
+ nint = m_CF * eint;
+
+ } else {
+ nint = m_CF * (powl ((eint + 1.0), (static_cast<FCALC_DOUBLE>(m_PF) / static_cast<FCALC_DOUBLE>(m_CF))) - 1.0);
+ }
+ } else
+ nint = logl (powl (eint + 1.0, m_PF));
+
+ return nint;
+}
+
+FCALC_DOUBLE MyMoneyFinancialCalculator::interestDue(void) const
+{
+ FCALC_DOUBLE eint = eff_int();
+
+ return (m_pv + (m_bep ? m_pmt : 0.0)) * eint;
+}
+
diff --git a/kmymoney2/mymoney/mymoneyfinancialcalculator.h b/kmymoney2/mymoney/mymoneyfinancialcalculator.h
new file mode 100644
index 0000000..c603d12
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyfinancialcalculator.h
@@ -0,0 +1,317 @@
+/***************************************************************************
+ mymoneyfinancialcalculator.h - description
+ -------------------
+ begin : Tue Oct 21 2003
+ copyright : (C) 2000-2003 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYFINANCIALCALCULATOR_H
+#define MYMONEYFINANCIALCALCULATOR_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <cmath>
+// ----------------------------------------------------------------------------
+// QT Includes
+
+// ----------------------------------------------------------------------------
+// KDE Includes
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include <kmymoney/export.h>
+
+#ifdef _GLIBCPP_HAVE_MODFL
+#define HAVE_LONG_DOUBLE 1
+#endif
+
+#ifndef HAVE_LONG_DOUBLE
+#define HAVE_LONG_DOUBLE 0
+#endif
+
+#if HAVE_LONG_DOUBLE
+#define FCALC_DOUBLE long double
+#else
+#define FCALC_DOUBLE double
+#define modfl(a,b) modf(a,b)
+#define roundl(a) round(a)
+#define powl(a,b) pow(a,b)
+#define expl(a) exp(a)
+#define logl(a) log(a)
+#define floorl(a) floor(a)
+#define fabsl(a) fabs(a)
+#endif
+
+/**
+ * @author Thomas Baumgart
+ */
+
+/**
+ * This class implements the financial calculator as found in GNUCash.
+ * For a detailed description of the algorithms see
+ * gnucash-1.8.5/src/doc/finutil.html.
+ */
+class KMYMONEY_EXPORT MyMoneyFinancialCalculator
+{
+public:
+ MyMoneyFinancialCalculator();
+ ~MyMoneyFinancialCalculator();
+
+ /**
+ * This method calculates the number of payments required to amortize
+ * the loan. ir, pv, fv and pmt must be set. It sets the member variable
+ * m_npp with the calculated value.
+ *
+ * @return number of periodic payments
+ *
+ * @exception If one of the required values is not set, a MyMoneyException
+ * will be thrown
+ */
+ FCALC_DOUBLE numPayments();
+
+ /**
+ * This method calculates the amount of the payment (amortization and interest)
+ * for the loan. ir, pv, fv and npp must be set. It sets the member variable
+ * m_pmt with the calculated value.
+ *
+ * @return amount of payment
+ *
+ * @exception If one of the required values is not set, a MyMoneyException
+ * will be thrown
+ */
+ FCALC_DOUBLE payment();
+
+ /**
+ * This method calculates the present value
+ * for the loan. ir, pmt, fv and npp must be set. It sets the member variable
+ * m_pv with the calculated value.
+ *
+ * @return present value of loan
+ *
+ * @exception If one of the required values is not set, a MyMoneyException
+ * will be thrown
+ */
+ FCALC_DOUBLE presentValue();
+
+ /**
+ * This method calculates the future value
+ * for the loan. ir, pmt, pv and npp must be set. It sets the member variable
+ * m_fv with the calculated value.
+ *
+ * @return future value of loan
+ *
+ * @exception If one of the required values is not set, a MyMoneyException
+ * will be thrown
+ */
+ FCALC_DOUBLE futureValue();
+
+ /**
+ * This method calculates the nominal interest rate
+ * for the loan. fv, pmt, pv and npp must be set. It sets the member variable
+ * m_ir with the calculated value.
+ *
+ * @return interest rate of the loan
+ *
+ * @exception If one of the required values is not set, a MyMoneyException
+ * will be thrown
+ */
+ FCALC_DOUBLE interestRate();
+
+ /**
+ * This method calculates the interest due for the next payment according
+ * to the equation
+ *
+ * id[n] = (pv[n-1] + (X * pmt)) * i
+ *
+ * with
+ *
+ * - pv[n-1]\n
+ * the present value at the end of the last period
+ * - X\n
+ * 0 for end of period payments, 1 for beginning of period payments
+ * - pmt\n
+ * the periodic payment amount and
+ * - i\n
+ * the effective interest rate
+ *
+ * pv[n-1] will be the value as set with setPv(), i will be calculated
+ * from the nominal interest rate as set with setIr(), pmt will be the
+ * value as set with setPmt() and X is determined by the argument to
+ * setBep().
+ *
+ * @return the interest amount
+ */
+ FCALC_DOUBLE interestDue(void) const;
+
+ /**
+ * This method sets the rounding precision to @p prec fractional
+ * digits. The default of @p is 2. Rounding is applied to pv, pmt
+ * and fv.
+ *
+ * @param prec Number of fractional digits after rounding.
+ */
+ void setPrec(const unsigned short prec = 2);
+
+ /**
+ * This method sets the number of payment periods to the value
+ * passed in parameter @p npp. The length of a period is controlled
+ * via setPF().
+ *
+ * @param npp number of payment periods
+ */
+ void setNpp(const FCALC_DOUBLE npp);
+
+ FCALC_DOUBLE npp(void) const { return m_npp; };
+
+ /**
+ * This method sets the payment frequency. The parameter @p PF
+ * specifies the payments per year.
+ *
+ * - 1 == annual
+ * - 2 == semi-annual
+ * - 3 == tri-annual
+ * - 4 == quaterly
+ * - 6 == bi-monthly
+ * - 12 == monthly
+ * - 24 == semi-monthly
+ * - 26 == bi-weekly
+ * - 52 == weekly
+ * - 360 == daily
+ * - 365 == daily
+ *
+ * @param PF length of payment period (default is 12 - monthly)
+ */
+ void setPF(const unsigned short PF = 12);
+
+ /**
+ * This method sets the compounding frequency. The parameter @p CF
+ * specifies the compounding period per year.
+ *
+ * - 1 == annual
+ * - 2 == semi-annual
+ * - 3 == tri-annual
+ * - 4 == quaterly
+ * - 6 == bi-monthly
+ * - 12 == monthly
+ * - 24 == semi-monthly
+ * - 26 == bi-weekly
+ * - 52 == weekly
+ * - 360 == daily
+ * - 365 == daily
+ *
+ * @param CF length of compounding period (default is 12 - monthly)
+ */
+ void setCF(const unsigned short CF = 12);
+
+ /**
+ * This method controls whether the interest will be calculated
+ * at the end of the payment period of at it's beginning.
+ *
+ * @param bep if @p false (default) then the interest is due at the
+ * end of the payment period, if @p true at it's beginning.
+ */
+ void setBep(const bool bep = false);
+
+ /**
+ * This method controls whether the interest is compounded in periods
+ * or continously.
+ *
+ * @param disc if @p true (default) then the interest is compounded in
+ * periods, if @p false continously.
+ */
+ void setDisc(const bool disc = true);
+
+ /**
+ * This method sets the nominal interest rate to the value passed
+ * in the argument @p ir.
+ *
+ * @param ir nominal interest rate
+ */
+ void setIr(const FCALC_DOUBLE ir);
+
+ FCALC_DOUBLE ir(void) const { return m_ir; };
+
+ /**
+ * This method sets the present value to the value passed
+ * in the argument @p pv.
+ *
+ * @param pv present value
+ */
+ void setPv(const FCALC_DOUBLE pv);
+
+ FCALC_DOUBLE pv(void) const { return m_pv; };
+
+ /**
+ * This method sets the payment amount to the value passed
+ * in the argument @p pmt.
+ *
+ * @param pmt payment amount
+ */
+ void setPmt(const FCALC_DOUBLE pmt);
+
+ FCALC_DOUBLE pmt(void) const { return m_pmt; };
+
+ /**
+ * This method sets the future value to the value passed
+ * in the argument @p fv.
+ *
+ * @param fv future value
+ */
+ void setFv(const FCALC_DOUBLE fv);
+
+ FCALC_DOUBLE fv(void) const { return m_fv; };
+
+private:
+ FCALC_DOUBLE eff_int(void) const;
+ FCALC_DOUBLE nom_int(const FCALC_DOUBLE eint) const;
+ FCALC_DOUBLE rnd(const FCALC_DOUBLE x) const;
+
+ FCALC_DOUBLE _Ax(const FCALC_DOUBLE eint) const;
+ FCALC_DOUBLE _Bx(const FCALC_DOUBLE eint) const;
+ FCALC_DOUBLE _Cx(const FCALC_DOUBLE eint) const;
+ FCALC_DOUBLE _fi(const FCALC_DOUBLE eint) const;
+ FCALC_DOUBLE _fip(const FCALC_DOUBLE eint) const;
+
+private:
+ FCALC_DOUBLE m_ir; // nominal interest rate
+ FCALC_DOUBLE m_pv; // present value
+ FCALC_DOUBLE m_pmt; // periodic payment
+ FCALC_DOUBLE m_fv; // future value
+ FCALC_DOUBLE m_npp; // number of payment periods
+
+ unsigned short m_CF; // compounding frequency
+ unsigned short m_PF; // payment frequency
+
+ unsigned short m_prec; // precision for roundoff for pv, pmt and fv
+ // i is not rounded, n is integer
+
+ bool m_bep; // beginning/end of period payment flag
+ bool m_disc; // discrete/continous compounding flag
+
+ unsigned short m_mask; // available value mask
+ #define PV_SET 0x0001
+ #define IR_SET 0x0002
+ #define PMT_SET 0x0004
+ #define NPP_SET 0x0008
+ #define FV_SET 0x0010
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneyfinancialcalculatortest.cpp b/kmymoney2/mymoney/mymoneyfinancialcalculatortest.cpp
new file mode 100644
index 0000000..fa5b1a3
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyfinancialcalculatortest.cpp
@@ -0,0 +1,189 @@
+/***************************************************************************
+ mymoneyfinancialcalculatortest.cpp
+ -------------------
+ copyright : (C) 2003 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 <iostream>
+
+#include <math.h>
+#include "mymoneymoney.h"
+#include "mymoneyfinancialcalculatortest.h"
+
+MyMoneyFinancialCalculatorTest::MyMoneyFinancialCalculatorTest () {}
+
+
+void MyMoneyFinancialCalculatorTest::setUp () {
+ m = new MyMoneyFinancialCalculator;
+}
+
+void MyMoneyFinancialCalculatorTest::tearDown () {
+ delete m;
+}
+
+void MyMoneyFinancialCalculatorTest::testEmptyConstructor() {
+ CPPUNIT_ASSERT(m->m_ir == 0.0);
+ CPPUNIT_ASSERT(m->m_pv == 0.0);
+ CPPUNIT_ASSERT(m->m_pmt == 0.0);
+ CPPUNIT_ASSERT(m->m_fv == 0.0);
+ CPPUNIT_ASSERT(m->m_npp == 0);
+ CPPUNIT_ASSERT(m->m_CF == 12);
+ CPPUNIT_ASSERT(m->m_PF == 12);
+ CPPUNIT_ASSERT(m->m_prec == 2);
+ CPPUNIT_ASSERT(m->m_bep == false);
+ CPPUNIT_ASSERT(m->m_disc == true);
+ CPPUNIT_ASSERT(m->m_mask == 0);
+}
+
+void MyMoneyFinancialCalculatorTest::testSetPrec() {
+ m->setPrec(3);
+ CPPUNIT_ASSERT(m->m_prec == 3);
+}
+
+void MyMoneyFinancialCalculatorTest::testSetNpp() {
+ m->setNpp(20);
+ CPPUNIT_ASSERT(m->m_npp == 20);
+ CPPUNIT_ASSERT(m->m_mask == NPP_SET);
+}
+
+void MyMoneyFinancialCalculatorTest::testSetPF() {
+ m->setPF(1);
+ CPPUNIT_ASSERT(m->m_PF == 1);
+ m->setPF();
+ CPPUNIT_ASSERT(m->m_PF == 12);
+}
+
+void MyMoneyFinancialCalculatorTest::testSetCF() {
+ m->setCF(1);
+ CPPUNIT_ASSERT(m->m_CF == 1);
+ m->setCF();
+ CPPUNIT_ASSERT(m->m_CF == 12);
+}
+
+void MyMoneyFinancialCalculatorTest::testSetBep() {
+ m->setBep(true);
+ CPPUNIT_ASSERT(m->m_bep == true);
+ m->setBep();
+ CPPUNIT_ASSERT(m->m_bep == false);
+}
+
+void MyMoneyFinancialCalculatorTest::testSetDisc() {
+ m->setDisc(false);
+ CPPUNIT_ASSERT(m->m_disc == false);
+ m->setDisc();
+ CPPUNIT_ASSERT(m->m_disc == true);
+}
+
+void MyMoneyFinancialCalculatorTest::testSetIr() {
+ m->setIr(12.3);
+ CPPUNIT_ASSERT(m->m_ir == 12.3);
+ CPPUNIT_ASSERT(m->m_mask == IR_SET);
+}
+
+void MyMoneyFinancialCalculatorTest::testSetPv() {
+ m->setPv(23.4);
+ CPPUNIT_ASSERT(m->m_pv == 23.4);
+ CPPUNIT_ASSERT(m->m_mask == PV_SET);
+}
+
+void MyMoneyFinancialCalculatorTest::testSetPmt() {
+ m->setPmt(34.5);
+ CPPUNIT_ASSERT(m->m_pmt == 34.5);
+ CPPUNIT_ASSERT(m->m_mask == PMT_SET);
+}
+
+void MyMoneyFinancialCalculatorTest::testSetFv() {
+ m->setFv(45.6);
+ CPPUNIT_ASSERT(m->m_fv == 45.6);
+ CPPUNIT_ASSERT(m->m_mask == FV_SET);
+}
+
+void MyMoneyFinancialCalculatorTest::testCombinedSet() {
+ m->setNpp(20);
+ m->setIr(12.3);
+ m->setPv(23.4);
+ m->setPmt(34.5);
+ m->setFv(45.6);
+
+ CPPUNIT_ASSERT(m->m_mask == (NPP_SET | PV_SET | IR_SET | PMT_SET | FV_SET));
+}
+
+void MyMoneyFinancialCalculatorTest::testNumPayments() {
+ m->setPF(12);
+ m->setCF(12);
+ try {
+ m->numPayments();
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ try {
+ m->setPv(-80000.0);
+ m->numPayments();
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ try {
+ m->setIr(12.0);
+ m->numPayments();
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ try {
+ m->setPmt(7108.0);
+ m->numPayments();
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ try {
+ m->setFv(0.0);
+ m->numPayments();
+ } catch (MyMoneyException *e) {
+ CPPUNIT_FAIL("Unexpected exception");
+ delete e;
+ }
+
+ CPPUNIT_ASSERT(roundl(m->m_npp) == 12);
+}
+
+void MyMoneyFinancialCalculatorTest::testUseCase1() {
+ m->setPv(-300000.0);
+ m->setIr(5.0);
+ m->setNpp(360);
+ m->setFv(0.0);
+ m->setPF(12);
+ MyMoneyMoney res(m->payment());
+ CPPUNIT_ASSERT(res == MyMoneyMoney(161046,100));
+
+ res = MyMoneyMoney(m->futureValue());
+ CPPUNIT_ASSERT(res == MyMoneyMoney(405,100));
+}
+
+void MyMoneyFinancialCalculatorTest::testUseCase2() {
+ m->setPv(-320000.0);
+ m->setIr(5.0);
+ m->setNpp(360);
+ m->setFv(0.0);
+ m->setPF(12);
+ MyMoneyMoney res(m->payment());
+ CPPUNIT_ASSERT(res == MyMoneyMoney(171783,100));
+
+ res = MyMoneyMoney(m->futureValue());
+ CPPUNIT_ASSERT(res == MyMoneyMoney(-67,100));
+}
diff --git a/kmymoney2/mymoney/mymoneyfinancialcalculatortest.h b/kmymoney2/mymoney/mymoneyfinancialcalculatortest.h
new file mode 100644
index 0000000..f1d6b3b
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyfinancialcalculatortest.h
@@ -0,0 +1,72 @@
+/***************************************************************************
+ mymoneyfinancialcalculatortest.h
+ -------------------
+ copyright : (C) 2003 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 __MYMONEYFINANCIALCALCULATORTEST_H__
+#define __MYMONEYFINANCIALCALCULATORTEST_H__
+
+#include <cppunit/extensions/HelperMacros.h>
+#include "autotest.h"
+
+#define private public
+#define protected public
+#include "mymoneyfinancialcalculator.h"
+#undef private
+#undef protected
+
+class MyMoneyFinancialCalculatorTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(MyMoneyFinancialCalculatorTest);
+ CPPUNIT_TEST(testEmptyConstructor);
+ CPPUNIT_TEST(testSetPrec);
+ CPPUNIT_TEST(testSetNpp);
+ CPPUNIT_TEST(testSetPF);
+ CPPUNIT_TEST(testSetCF);
+ CPPUNIT_TEST(testSetBep);
+ CPPUNIT_TEST(testSetDisc);
+ CPPUNIT_TEST(testSetIr);
+ CPPUNIT_TEST(testSetPv);
+ CPPUNIT_TEST(testSetPmt);
+ CPPUNIT_TEST(testSetFv);
+ CPPUNIT_TEST(testCombinedSet);
+ CPPUNIT_TEST(testNumPayments);
+ CPPUNIT_TEST(testUseCase1);
+ CPPUNIT_TEST(testUseCase2);
+ CPPUNIT_TEST_SUITE_END();
+protected:
+ MyMoneyFinancialCalculator *m;
+
+public:
+ MyMoneyFinancialCalculatorTest ();
+
+ void setUp ();
+ void tearDown ();
+ void testEmptyConstructor();
+ void testSetPrec();
+ void testSetNpp();
+ void testSetPF();
+ void testSetCF();
+ void testSetBep();
+ void testSetDisc();
+ void testSetIr();
+ void testSetPv();
+ void testSetPmt();
+ void testSetFv();
+ void testCombinedSet();
+ void testNumPayments();
+ void testUseCase1();
+ void testUseCase2();
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneyforecast.cpp b/kmymoney2/mymoney/mymoneyforecast.cpp
new file mode 100644
index 0000000..56125ea
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyforecast.cpp
@@ -0,0 +1,1340 @@
+/***************************************************************************
+ mymoneyforecast.cpp
+ -------------------
+ begin : Wed May 30 2007
+ copyright : (C) 2007 by Alvaro Soliverez
+ email : asoliverez@gmail.com
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstring.h>
+#include <qdatetime.h>
+
+// ----------------------------------------------------------------------------
+// KDE Includes
+#include <kdebug.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "mymoneyforecast.h"
+#include "../kmymoneyglobalsettings.h"
+#include "mymoneyfile.h"
+#include "mymoneytransactionfilter.h"
+#include "mymoneyfinancialcalculator.h"
+
+MyMoneyForecast::MyMoneyForecast() :
+ m_forecastMethod(0),
+ m_historyMethod(0),
+ m_skipOpeningDate(true),
+ m_includeUnusedAccounts(false),
+ m_forecastDone(false)
+{
+ setForecastCycles(KMyMoneyGlobalSettings::forecastCycles());
+ setAccountsCycle(KMyMoneyGlobalSettings::forecastAccountCycle());
+ setHistoryStartDate(QDate::currentDate().addDays(-forecastCycles()*accountsCycle()));
+ setHistoryEndDate(QDate::currentDate().addDays(-1));
+ setForecastDays(KMyMoneyGlobalSettings::forecastDays());
+ setBeginForecastDay(KMyMoneyGlobalSettings::beginForecastDay());
+ setForecastMethod(KMyMoneyGlobalSettings::forecastMethod());
+ setHistoryMethod(KMyMoneyGlobalSettings::historyMethod());
+ setIncludeFutureTransactions(KMyMoneyGlobalSettings::includeFutureTransactions());
+ setIncludeScheduledTransactions(KMyMoneyGlobalSettings::includeScheduledTransactions());
+}
+
+
+void MyMoneyForecast::doForecast()
+{
+ int fDays = calculateBeginForecastDay();
+ int fMethod = forecastMethod();
+ int fAccCycle = accountsCycle();
+ int fCycles = forecastCycles();
+
+ //validate settings
+ if(fAccCycle < 1
+ || fCycles < 1
+ || fDays < 1)
+ {
+ throw new MYMONEYEXCEPTION("Illegal settings when calling doForecast. Settings must be higher than 0");
+ }
+
+ //initialize global variables
+ setForecastDays(fDays);
+ setForecastStartDate(QDate::currentDate().addDays(1));
+ setForecastEndDate(QDate::currentDate().addDays(fDays));
+ setAccountsCycle(fAccCycle);
+ setForecastCycles(fCycles);
+ setHistoryStartDate(forecastCycles() * accountsCycle());
+ setHistoryEndDate(QDate::currentDate().addDays(-1)); //yesterday
+
+ //clear all data before calculating
+ m_accountListPast.clear();
+ m_accountList.clear();
+ m_accountTrendList.clear();
+
+ //set forecast accounts
+ setForecastAccountList();
+
+ switch(fMethod)
+ {
+ case eScheduled:
+ doFutureScheduledForecast();
+ calculateScheduledDailyBalances();
+ break;
+ case eHistoric:
+ pastTransactions();
+ calculateHistoricDailyBalances();
+ break;
+ default:
+ break;
+ }
+
+ //flag the forecast as done
+ m_forecastDone = true;
+}
+
+MyMoneyForecast::~MyMoneyForecast()
+{
+}
+
+void MyMoneyForecast::pastTransactions()
+{
+ MyMoneyFile* file = MyMoneyFile::instance();
+ MyMoneyTransactionFilter filter;
+
+ filter.setDateFilter(historyStartDate(), historyEndDate());
+ filter.setReportAllSplits(false);
+
+ QValueList<MyMoneyTransaction> transactions = file->transactionList(filter);
+ QValueList<MyMoneyTransaction>::const_iterator it_t = transactions.begin();
+
+ //Check past transactions
+ for(; it_t != transactions.end(); ++it_t ) {
+ const QValueList<MyMoneySplit>& splits = (*it_t).splits();
+ QValueList<MyMoneySplit>::const_iterator it_s = splits.begin();
+ for(; it_s != splits.end(); ++it_s ) {
+ if(!(*it_s).shares().isZero()) {
+ MyMoneyAccount acc = file->account((*it_s).accountId());
+
+ //workaround for stock accounts which have faulty opening dates
+ QDate openingDate;
+ if(acc.accountType() == MyMoneyAccount::Stock) {
+ MyMoneyAccount parentAccount = file->account(acc.parentAccountId());
+ openingDate = parentAccount.openingDate();
+ } else {
+ openingDate = acc.openingDate();
+ }
+
+ if(isForecastAccount(acc) //If it is one of the accounts we are checking, add the amount of the transaction
+ && ( (openingDate < (*it_t).postDate() && skipOpeningDate())
+ || !skipOpeningDate() ) ){ //don't take the opening day of the account to calculate balance
+ dailyBalances balance;
+ //FIXME deal with leap years
+ balance = m_accountListPast[acc.id()];
+ if(acc.accountType() == MyMoneyAccount::Income) {//if it is income, the balance is stored as negative number
+ balance[(*it_t).postDate()] += ((*it_s).shares() * MyMoneyMoney(-1, 1));
+ } else {
+ balance[(*it_t).postDate()] += (*it_s).shares();
+ }
+ // check if this is a new account for us
+ m_accountListPast[acc.id()] = balance;
+ }
+ }
+ }
+ }
+
+ //purge those accounts with no transactions on the period
+ if(isIncludingUnusedAccounts() == false)
+ purgeForecastAccountsList(m_accountListPast);
+
+ //calculate running sum
+ QMap<QString, QString>::Iterator it_n;
+ for(it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
+ MyMoneyAccount acc = file->account(*it_n);
+ m_accountListPast[acc.id()][historyStartDate().addDays(-1)] = file->balance(acc.id(), historyStartDate().addDays(-1));
+ for(QDate it_date = historyStartDate(); it_date <= historyEndDate(); ) {
+ m_accountListPast[acc.id()][it_date] += m_accountListPast[acc.id()][it_date.addDays(-1)]; //Running sum
+ it_date = it_date.addDays(1);
+ }
+ }
+
+ //adjust value of investments to deep currency
+ for ( it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n ) {
+ MyMoneyAccount acc = file->account ( *it_n );
+
+ if ( acc.isInvest() ) {
+ //get the id of the security for that account
+ MyMoneySecurity undersecurity = file->security ( acc.currencyId() );
+ if ( ! undersecurity.isCurrency() ) //only do it if the security is not an actual currency
+ {
+ MyMoneyMoney rate = MyMoneyMoney ( 1, 1 ); //set the default value
+ MyMoneyPrice price;
+
+ for ( QDate it_date = historyStartDate().addDays(-1) ; it_date <= historyEndDate();) {
+ //get the price for the tradingCurrency that day
+ price = file->price ( undersecurity.id(), undersecurity.tradingCurrency(), it_date );
+ if ( price.isValid() )
+ {
+ rate = price.rate ( undersecurity.tradingCurrency() );
+ }
+ //value is the amount of shares multiplied by the rate of the deep currency
+ m_accountListPast[acc.id() ][it_date] = m_accountListPast[acc.id() ][it_date] * rate;
+ it_date = it_date.addDays(1);
+ }
+ }
+ }
+ }
+}
+
+bool MyMoneyForecast::isForecastAccount(const MyMoneyAccount& acc)
+{
+ if(m_nameIdx.isEmpty())
+ {
+ setForecastAccountList();
+ }
+ QMap<QString, QString>::Iterator it_n;
+ for(it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
+ if(*it_n == acc.id())
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+void MyMoneyForecast::calculateAccountTrendList()
+{
+ MyMoneyFile* file = MyMoneyFile::instance();
+ int auxForecastTerms;
+ int totalWeight = 0;
+
+ //Calculate account trends
+ QMap<QString, QString>::Iterator it_n;
+ for(it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
+ MyMoneyAccount acc = file->account(*it_n);
+ m_accountTrendList[acc.id()][0] = MyMoneyMoney(0,1); // for today, the trend is 0
+
+ auxForecastTerms = forecastCycles();
+ if(skipOpeningDate()) {
+
+ QDate openingDate;
+ if(acc.accountType() == MyMoneyAccount::Stock) {
+ MyMoneyAccount parentAccount = file->account(acc.parentAccountId());
+ openingDate = parentAccount.openingDate();
+ } else {
+ openingDate = acc.openingDate();
+ }
+
+ if(openingDate > historyStartDate() ) { //if acc opened after forecast period
+ auxForecastTerms = 1 + ((openingDate.daysTo(historyEndDate()) + 1)/ accountsCycle()); // set forecastTerms to a lower value, to calculate only based on how long this account was opened
+ }
+ }
+
+ switch (historyMethod())
+ {
+ //moving average
+ case 0:
+ {
+ for(int t_day = 1; t_day <= accountsCycle(); t_day++)
+ m_accountTrendList[acc.id()][t_day] = accountMovingAverage(acc, t_day, auxForecastTerms); //moving average
+ break;
+ }
+ //weighted moving average
+ case 1:
+ {
+ //calculate total weight for moving average
+ if(auxForecastTerms == forecastCycles()) {
+ totalWeight = (auxForecastTerms * (auxForecastTerms + 1))/2; //totalWeight is the triangular number of auxForecastTerms
+ } else {
+ //if only taking a few periods, totalWeight is the sum of the weight for most recent periods
+ for(int i = 1, w = forecastCycles(); i <= auxForecastTerms; ++i, --w)
+ totalWeight += w;
+ }
+ for(int t_day = 1; t_day <= accountsCycle(); t_day++)
+ m_accountTrendList[acc.id()][t_day] = accountWeightedMovingAverage(acc, t_day, totalWeight);
+ break;
+ }
+ case 2:
+ {
+ //calculate mean term
+ MyMoneyMoney meanTerms = MyMoneyMoney((auxForecastTerms * (auxForecastTerms + 1))/2, 1) / MyMoneyMoney(auxForecastTerms, 1);
+
+ for(int t_day = 1; t_day <= accountsCycle(); t_day++)
+ m_accountTrendList[acc.id()][t_day] = accountLinearRegression(acc, t_day, auxForecastTerms, meanTerms);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+}
+
+QValueList<MyMoneyAccount> MyMoneyForecast::forecastAccountList(void)
+{
+ MyMoneyFile* file = MyMoneyFile::instance();
+
+ QValueList<MyMoneyAccount> accList;
+ //Get all accounts from the file and check if they are of the right type to calculate forecast
+ file->accountList(accList);
+ QValueList<MyMoneyAccount>::iterator accList_t = accList.begin();
+ for(; accList_t != accList.end(); ) {
+ MyMoneyAccount acc = *accList_t;
+ if(acc.isClosed() //check the account is not closed
+ || (!acc.isAssetLiability()) ) {
+ //|| (acc.accountType() == MyMoneyAccount::Investment) ) {//check that it is not an Investment account and only include Stock accounts
+ accList.remove(accList_t); //remove the account if it is not of the correct type
+ accList_t = accList.begin();
+ } else {
+ ++accList_t;
+ }
+ }
+ return accList;
+}
+
+QValueList<MyMoneyAccount> MyMoneyForecast::accountList(void)
+{
+ MyMoneyFile* file = MyMoneyFile::instance();
+
+ QValueList<MyMoneyAccount> accList;
+ QStringList emptyStringList;
+ //Get all accounts from the file and check if they are present
+ file->accountList(accList, emptyStringList, false);
+ QValueList<MyMoneyAccount>::iterator accList_t = accList.begin();
+ for(; accList_t != accList.end(); ) {
+ MyMoneyAccount acc = *accList_t;
+ if(!isForecastAccount( acc ) ) {
+ accList.remove(accList_t); //remove the account
+ accList_t = accList.begin();
+ } else {
+ ++accList_t;
+ }
+ }
+ return accList;
+}
+
+MyMoneyMoney MyMoneyForecast::calculateAccountTrend(const MyMoneyAccount& acc, int trendDays)
+{
+ MyMoneyFile* file = MyMoneyFile::instance();
+ MyMoneyTransactionFilter filter;
+ MyMoneyMoney netIncome;
+ QDate startDate;
+ QDate openingDate = acc.openingDate();
+
+ //validate arguments
+ if(trendDays < 1)
+ {
+ throw new MYMONEYEXCEPTION("Illegal arguments when calling calculateAccountTrend. trendDays must be higher than 0");
+ }
+
+ //If it is a new account, we don't take into account the first day
+ //because it is usually a weird one and it would mess up the trend
+ if(openingDate.daysTo(QDate::currentDate())<trendDays){
+ startDate = (acc.openingDate()).addDays(1);
+ }
+ else {
+ startDate = QDate::currentDate().addDays(-trendDays);
+ }
+ //get all transactions for the period
+ filter.setDateFilter(startDate, QDate::currentDate());
+ if(acc.accountGroup() == MyMoneyAccount::Income
+ || acc.accountGroup() == MyMoneyAccount::Expense) {
+ filter.addCategory(acc.id());
+ } else {
+ filter.addAccount(acc.id());
+ }
+
+ filter.setReportAllSplits(false);
+ QValueList<MyMoneyTransaction> transactions = file->transactionList(filter);
+ QValueList<MyMoneyTransaction>::const_iterator it_t = transactions.begin();
+
+ //add all transactions for that account
+ for(; it_t != transactions.end(); ++it_t ) {
+ const QValueList<MyMoneySplit>& splits = (*it_t).splits();
+ QValueList<MyMoneySplit>::const_iterator it_s = splits.begin();
+ for(; it_s != splits.end(); ++it_s ) {
+ if(!(*it_s).shares().isZero()) {
+ if(acc.id()==(*it_s).accountId()) netIncome += (*it_s).value();
+ }
+ }
+ }
+
+ //calculate trend of the account in the past period
+ MyMoneyMoney accTrend;
+
+ //don't take into account the first day of the account
+ if(openingDate.daysTo(QDate::currentDate())<trendDays) {
+ accTrend = netIncome/MyMoneyMoney(openingDate.daysTo(QDate::currentDate())-1,1);
+ } else {
+ accTrend = netIncome/MyMoneyMoney(trendDays,1);
+ }
+ return accTrend;
+}
+
+MyMoneyMoney MyMoneyForecast::accountMovingAverage(const MyMoneyAccount &acc, const int trendDay, const int forecastTerms)
+{
+ //Calculate a daily trend for the account based on the accounts of a given number of terms
+ //With a term of 1 month and 3 terms, it calculates the trend taking the transactions occurred at that day and the day before,
+ //for the last 3 months
+ MyMoneyMoney balanceVariation;
+
+ for(int it_terms = 0; (trendDay+(accountsCycle()*it_terms)) <= historyDays(); ++it_terms) //sum for each term
+ {
+ MyMoneyMoney balanceBefore = m_accountListPast[acc.id()][historyStartDate().addDays(trendDay+(accountsCycle()*it_terms)-2)]; //get balance for the day before
+ MyMoneyMoney balanceAfter = m_accountListPast[acc.id()][historyStartDate().addDays(trendDay+(accountsCycle()*it_terms)-1)];
+ balanceVariation += (balanceAfter - balanceBefore); //add the balance variation between days
+ }
+ //calculate average of the variations
+ return (balanceVariation / MyMoneyMoney(forecastTerms,1)).convert(10000);
+}
+
+MyMoneyMoney MyMoneyForecast::accountWeightedMovingAverage(const MyMoneyAccount &acc, const int trendDay, const int totalWeight)
+{
+ MyMoneyMoney balanceVariation;
+
+ for(int it_terms = 0, weight = 1; (trendDay+(accountsCycle()*it_terms)) <= historyDays(); ++it_terms, ++weight) //sum for each term multiplied by weight
+ {
+ MyMoneyMoney balanceBefore = m_accountListPast[acc.id()][historyStartDate().addDays(trendDay+(accountsCycle()*it_terms)-2)]; //get balance for the day before
+ MyMoneyMoney balanceAfter = m_accountListPast[acc.id()][historyStartDate().addDays(trendDay+(accountsCycle()*it_terms)-1)];
+ balanceVariation += ( (balanceAfter - balanceBefore) * MyMoneyMoney(weight, 1) ); //add the balance variation between days multiplied by its weight
+ }
+ //calculate average of the variations
+ return (balanceVariation / MyMoneyMoney(totalWeight, 1)).convert(10000);
+}
+
+MyMoneyMoney MyMoneyForecast::accountLinearRegression(const MyMoneyAccount &acc, const int trendDay, const int actualTerms, const MyMoneyMoney meanTerms)
+{
+ MyMoneyMoney meanBalance, totalBalance, totalTerms;
+ totalTerms = MyMoneyMoney(actualTerms, 1);
+
+ //calculate mean balance
+ for(int it_terms = forecastCycles() - actualTerms; (trendDay+(accountsCycle()*it_terms)) <= historyDays(); ++it_terms) //sum for each term
+ {
+ totalBalance += m_accountListPast[acc.id()][historyStartDate().addDays(trendDay+(accountsCycle()*it_terms)-1)];
+ }
+ meanBalance = totalBalance / MyMoneyMoney(actualTerms,1);
+ meanBalance = meanBalance.convert(10000);
+
+ //calculate b1
+
+ //first calculate x - mean x multiplied by y - mean y
+ MyMoneyMoney totalXY, totalSqX;
+ for(int it_terms = forecastCycles() - actualTerms, term = 1; (trendDay+(accountsCycle()*it_terms)) <= historyDays(); ++it_terms, ++term) //sum for each term
+ {
+ MyMoneyMoney balance = m_accountListPast[acc.id()][historyStartDate().addDays(trendDay+(accountsCycle()*it_terms)-1)];
+
+ MyMoneyMoney balMeanBal = balance - meanBalance;
+ MyMoneyMoney termMeanTerm = (MyMoneyMoney(term, 1) - meanTerms);
+
+ totalXY += (balMeanBal * termMeanTerm).convert(10000);
+
+ totalSqX += (termMeanTerm * termMeanTerm).convert(10000);
+ }
+ totalXY = (totalXY / MyMoneyMoney(actualTerms,1)).convert(10000);
+ totalSqX = (totalSqX / MyMoneyMoney(actualTerms,1)).convert(10000);
+
+ //check zero
+ if(totalSqX.isZero())
+ return MyMoneyMoney(0,1);
+
+ MyMoneyMoney linReg = (totalXY/totalSqX).convert(10000);
+
+ return linReg;
+}
+
+void MyMoneyForecast::calculateHistoricDailyBalances()
+{
+ MyMoneyFile* file = MyMoneyFile::instance();
+
+ calculateAccountTrendList();
+
+ //Calculate account daily balances
+ QMap<QString, QString>::ConstIterator it_n;
+ for(it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
+ MyMoneyAccount acc = file->account(*it_n);
+
+ //set the starting balance of the account
+ setStartingBalance(acc);
+
+ switch(historyMethod()) {
+ case 0:
+ case 1:
+ {
+ for(QDate f_day = forecastStartDate(); f_day <= forecastEndDate(); ) {
+ for(int t_day = 1; t_day <= accountsCycle(); ++t_day) {
+ MyMoneyMoney balanceDayBefore = m_accountList[acc.id()][(f_day.addDays(-1))];//balance of the day before
+ MyMoneyMoney accountDailyTrend = m_accountTrendList[acc.id()][t_day]; //trend for that day
+ //balance of the day is the balance of the day before multiplied by the trend for the day
+ m_accountList[acc.id()][f_day] = balanceDayBefore;
+ m_accountList[acc.id()][f_day] += accountDailyTrend; //movement trend for that particular day
+ m_accountList[acc.id()][f_day] = m_accountList[acc.id()][f_day].convert(acc.fraction());
+ //m_accountList[acc.id()][f_day] += m_accountListPast[acc.id()][f_day.addDays(-historyDays())];
+ f_day = f_day.addDays(1);
+ }
+ }
+ }
+ break;
+ case 2:
+ {
+ QDate baseDate = QDate::currentDate().addDays(-accountsCycle());
+ for(int t_day = 1; t_day <= accountsCycle(); ++t_day) {
+ int f_day = 1;
+ QDate fDate = baseDate.addDays(accountsCycle()+1);
+ while (fDate <= forecastEndDate()) {
+
+ //the calculation is based on the balance for the last month, that is then multiplied by the trend
+ m_accountList[acc.id()][fDate] = m_accountListPast[acc.id()][baseDate] + (m_accountTrendList[acc.id()][t_day] * MyMoneyMoney(f_day,1));
+ m_accountList[acc.id()][fDate] = m_accountList[acc.id()][fDate].convert(acc.fraction());
+ ++f_day;
+ fDate = baseDate.addDays(accountsCycle() * f_day);
+ }
+ baseDate = baseDate.addDays(1);
+ }
+ }
+ }
+ }
+}
+
+MyMoneyMoney MyMoneyForecast::forecastBalance(const MyMoneyAccount& acc, QDate forecastDate)
+{
+
+ dailyBalances balance;
+ MyMoneyMoney MM_amount = MyMoneyMoney(0,1);
+
+ //Check if acc is not a forecast account, return 0
+ if ( !isForecastAccount ( acc ) )
+ {
+ return MM_amount;
+ }
+
+ balance = m_accountList[acc.id() ];
+ if ( balance.contains ( forecastDate ) )
+ { //if the date is not in the forecast, it returns 0
+ MM_amount = m_accountList[acc.id() ][forecastDate];
+ }
+ return MM_amount;
+}
+
+/**
+ * Returns the forecast balance trend for account @a acc for offset @p int
+ * offset is days from current date, inside forecast days.
+ * Returns 0 if offset not in range of forecast days.
+ */
+MyMoneyMoney MyMoneyForecast::forecastBalance (const MyMoneyAccount& acc, int offset )
+{
+ QDate forecastDate = QDate::currentDate().addDays(offset);
+ return forecastBalance(acc, forecastDate);
+}
+
+void MyMoneyForecast::doFutureScheduledForecast(void)
+{
+ MyMoneyFile* file = MyMoneyFile::instance();
+
+ if(isIncludingFutureTransactions())
+ addFutureTransactions();
+
+ if(isIncludingScheduledTransactions())
+ addScheduledTransactions();
+
+ //do not show accounts with no transactions
+ if(!isIncludingUnusedAccounts())
+ purgeForecastAccountsList(m_accountList);
+
+ //adjust value of investments to deep currency
+ QMap<QString, QString>::ConstIterator it_n;
+ for ( it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n ) {
+ MyMoneyAccount acc = file->account ( *it_n );
+
+ if ( acc.isInvest() ) {
+ //get the id of the security for that account
+ MyMoneySecurity undersecurity = file->security ( acc.currencyId() );
+
+ //only do it if the security is not an actual currency
+ if ( ! undersecurity.isCurrency() )
+ {
+ //set the default value
+ MyMoneyMoney rate = MyMoneyMoney ( 1, 1 );
+ MyMoneyPrice price;
+
+ for (QDate it_day = QDate::currentDate(); it_day <= forecastEndDate(); ) {
+ //get the price for the tradingCurrency that day
+ price = file->price ( undersecurity.id(), undersecurity.tradingCurrency(), it_day );
+ if ( price.isValid() )
+ {
+ rate = price.rate ( undersecurity.tradingCurrency() );
+ }
+ //value is the amount of shares multiplied by the rate of the deep currency
+ m_accountList[acc.id() ][it_day] = m_accountList[acc.id() ][it_day] * rate;
+ it_day = it_day.addDays(1);
+ }
+ }
+ }
+ }
+}
+
+void MyMoneyForecast::addFutureTransactions(void)
+{
+ MyMoneyTransactionFilter filter;
+ MyMoneyFile* file = MyMoneyFile::instance();
+
+ // collect and process all transactions that have already been entered but
+ // are located in the future.
+ filter.setDateFilter(forecastStartDate(), forecastEndDate());
+ filter.setReportAllSplits(false);
+
+ QValueList<MyMoneyTransaction> transactions = file->transactionList(filter);
+ QValueList<MyMoneyTransaction>::const_iterator it_t = transactions.begin();
+
+ for(; it_t != transactions.end(); ++it_t ) {
+ const QValueList<MyMoneySplit>& splits = (*it_t).splits();
+ QValueList<MyMoneySplit>::const_iterator it_s = splits.begin();
+ for(; it_s != splits.end(); ++it_s ) {
+ if(!(*it_s).shares().isZero()) {
+ MyMoneyAccount acc = file->account((*it_s).accountId());
+ if(isForecastAccount(acc)) {
+ dailyBalances balance;
+ balance = m_accountList[acc.id()];
+ //if it is income, the balance is stored as negative number
+ if(acc.accountType() == MyMoneyAccount::Income) {
+ balance[(*it_t).postDate()] += ((*it_s).shares() * MyMoneyMoney(-1, 1));
+ } else {
+ balance[(*it_t).postDate()] += (*it_s).shares();
+ }
+ m_accountList[acc.id()] = balance;
+ }
+ }
+ }
+ }
+
+#if 0
+ QFile trcFile("forecast.csv");
+ trcFile.open(IO_WriteOnly);
+ QTextStream s(&trcFile);
+
+ {
+ s << "Already present transactions\n";
+ QMap<QString, dailyBalances>::Iterator it_a;
+ QMap<QString, QString>::ConstIterator it_n;
+ for(it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
+ MyMoneyAccount acc = file->account(*it_n);
+ it_a = m_accountList.find(*it_n);
+ s << "\"" << acc.name() << "\",";
+ for(int i = 0; i < 90; ++i) {
+ s << "\"" << (*it_a)[i].formatMoney("") << "\",";
+ }
+ s << "\n";
+ }
+}
+#endif
+
+}
+
+void MyMoneyForecast::addScheduledTransactions (void)
+{
+ MyMoneyFile* file = MyMoneyFile::instance();
+
+ // now process all the schedules that may have an impact
+ QValueList<MyMoneySchedule> schedule;
+
+ schedule = file->scheduleList("", MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY,
+ QDate::currentDate(), forecastEndDate());
+ if(schedule.count() > 0) {
+ QValueList<MyMoneySchedule>::Iterator it;
+ do {
+ qBubbleSort(schedule);
+ it = schedule.begin();
+ if(it == schedule.end())
+ break;
+
+ if((*it).isFinished()) {
+ schedule.erase(it);
+ continue;
+ }
+
+ QDate date = (*it).nextPayment((*it).lastPayment());
+ if(!date.isValid()) {
+ schedule.remove(it);
+ continue;
+ }
+
+ QDate nextDate = (*it).adjustedNextPayment((*it).lastPayment());
+ if (nextDate > forecastEndDate()) {
+ // We're done with this schedule, let's move on to the next
+ schedule.remove(it);
+ continue;
+ }
+
+ // found the next schedule. process it
+
+ MyMoneyAccount acc = (*it).account();
+
+ if(!acc.id().isEmpty()) {
+ try {
+ if(acc.accountType() != MyMoneyAccount::Investment) {
+ MyMoneyTransaction t = (*it).transaction();
+
+ // only process the entry, if it is still active
+ if(!(*it).isFinished() && nextDate != QDate()) {
+ // make sure we have all 'starting balances' so that the autocalc works
+ QValueList<MyMoneySplit>::const_iterator it_s;
+ QMap<QString, MyMoneyMoney> balanceMap;
+
+ for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s ) {
+ MyMoneyAccount acc = file->account((*it_s).accountId());
+ if(isForecastAccount(acc)) {
+ // collect all overdues on the first day
+ QDate forecastDate = nextDate;
+ if(QDate::currentDate() >= nextDate)
+ forecastDate = QDate::currentDate().addDays(1);
+
+ dailyBalances balance;
+ balance = m_accountList[acc.id()];
+ for(QDate f_day = QDate::currentDate(); f_day < forecastDate; ) {
+ balanceMap[acc.id()] += m_accountList[acc.id()][f_day];
+ f_day = f_day.addDays(1);
+ }
+ }
+ }
+
+ // take care of the autoCalc stuff
+ calculateAutoLoan(*it, t, balanceMap);
+
+ // now add the splits to the balances
+ for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s ) {
+ MyMoneyAccount acc = file->account((*it_s).accountId());
+ if(isForecastAccount(acc)) {
+ dailyBalances balance;
+ balance = m_accountList[acc.id()];
+ //int offset = QDate::currentDate().daysTo(nextDate);
+ //if(offset <= 0) { // collect all overdues on the first day
+ // offset = 1;
+ //}
+ // collect all overdues on the first day
+ QDate forecastDate = nextDate;
+ if(QDate::currentDate() >= nextDate)
+ forecastDate = QDate::currentDate().addDays(1);
+
+ if(acc.accountType() == MyMoneyAccount::Income) {
+ balance[forecastDate] += ((*it_s).shares() * MyMoneyMoney(-1, 1));
+ } else {
+ balance[forecastDate] += (*it_s).shares();
+ }
+ m_accountList[acc.id()] = balance;
+ }
+ }
+ }
+ }
+ (*it).setLastPayment(date);
+
+ } catch(MyMoneyException* e) {
+ kdDebug(2) << __func__ << " Schedule " << (*it).id() << " (" << (*it).name() << "): " << e->what() << endl;
+
+ schedule.remove(it);
+ delete e;
+ }
+ } else {
+ // remove schedule from list
+ schedule.remove(it);
+ }
+ }
+ while(1);
+ }
+
+#if 0
+{
+ s << "\n\nAdded scheduled transactions\n";
+ QMap<QString, dailyBalances>::Iterator it_a;
+ QMap<QString, QString>::ConstIterator it_n;
+ for(it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
+ MyMoneyAccount acc = file->account(*it_n);
+ it_a = m_accountList.find(*it_n);
+ s << "\"" << acc.name() << "\",";
+ for(int i = 0; i < 90; ++i) {
+ s << "\"" << (*it_a)[i].formatMoney("") << "\",";
+ }
+ s << "\n";
+}
+}
+#endif
+}
+
+void MyMoneyForecast::calculateScheduledDailyBalances (void)
+{
+ MyMoneyFile* file = MyMoneyFile::instance();
+
+ //Calculate account daily balances
+ QMap<QString, QString>::ConstIterator it_n;
+ for(it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
+ MyMoneyAccount acc = file->account(*it_n);
+
+ //set the starting balance of the account
+ setStartingBalance(acc);
+
+ for(QDate f_day = forecastStartDate(); f_day <= forecastEndDate(); ) {
+ MyMoneyMoney balanceDayBefore = m_accountList[acc.id()][(f_day.addDays(-1))];//balance of the day before
+ m_accountList[acc.id()][f_day] += balanceDayBefore; //running sum
+ f_day = f_day.addDays(1);
+ }
+ }
+
+
+}
+
+int MyMoneyForecast::daysToMinimumBalance(const MyMoneyAccount& acc)
+{
+ QString minimumBalance = acc.value("minBalanceAbsolute");
+ MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance);
+ dailyBalances balance;
+
+ //Check if acc is not a forecast account, return -1
+ if(!isForecastAccount(acc)) {
+ return -1;
+ }
+
+ balance = m_accountList[acc.id()];
+
+ for(QDate it_day = QDate::currentDate() ; it_day <= forecastEndDate(); ) {
+ if(minBalance > balance[it_day]) {
+ return QDate::currentDate().daysTo(it_day);
+ }
+ it_day = it_day.addDays(1);
+ }
+ return -1;
+}
+
+int MyMoneyForecast::daysToZeroBalance(const MyMoneyAccount& acc)
+{
+ dailyBalances balance;
+
+ //Check if acc is not a forecast account, return -1
+ if(!isForecastAccount(acc)) {
+ return -2;
+ }
+
+ balance = m_accountList[acc.id()];
+
+ if (acc.accountGroup() == MyMoneyAccount::Asset) {
+ for (QDate it_day = QDate::currentDate() ; it_day <= forecastEndDate(); )
+ {
+ if ( balance[it_day] < MyMoneyMoney ( 0, 1 ) )
+ {
+ return QDate::currentDate().daysTo(it_day);
+ }
+ it_day = it_day.addDays(1);
+ }
+ } else if (acc.accountGroup() == MyMoneyAccount::Liability) {
+ for (QDate it_day = QDate::currentDate() ; it_day <= forecastEndDate(); )
+ {
+ if ( balance[it_day] > MyMoneyMoney ( 0, 1 ) )
+ {
+ return QDate::currentDate().daysTo(it_day);
+ }
+ it_day = it_day.addDays(1);
+ }
+ }
+ return -1;
+}
+
+void MyMoneyForecast::setForecastAccountList(void)
+{
+
+ //get forecast accounts
+ QValueList<MyMoneyAccount> accList;
+ accList = forecastAccountList();
+
+ QValueList<MyMoneyAccount>::const_iterator accList_t = accList.begin();
+ for(; accList_t != accList.end(); ++accList_t ) {
+ MyMoneyAccount acc = *accList_t;
+ // check if this is a new account for us
+ if(m_nameIdx[acc.id()] != acc.id()) {
+ m_nameIdx[acc.id()] = acc.id();
+ }
+ }
+}
+
+MyMoneyMoney MyMoneyForecast::accountCycleVariation(const MyMoneyAccount& acc)
+{
+ MyMoneyMoney cycleVariation;
+
+ if (forecastMethod() == eHistoric) {
+ for(int t_day = 1; t_day <= accountsCycle() ; ++t_day) {
+ cycleVariation += m_accountTrendList[acc.id()][t_day];
+ }
+ }
+ return cycleVariation;
+}
+
+MyMoneyMoney MyMoneyForecast::accountTotalVariation(const MyMoneyAccount& acc)
+{
+ MyMoneyMoney totalVariation;
+
+ totalVariation = forecastBalance(acc, forecastEndDate()) - forecastBalance(acc, QDate::currentDate());
+ return totalVariation;
+}
+
+QValueList<QDate> MyMoneyForecast::accountMinimumBalanceDateList(const MyMoneyAccount& acc)
+{
+ QValueList<QDate> minBalanceList;
+ int daysToBeginDay;
+
+ daysToBeginDay = QDate::currentDate().daysTo(beginForecastDate());
+
+ for(int t_cycle = 0; ((t_cycle * accountsCycle()) + daysToBeginDay) < forecastDays() ; ++t_cycle) {
+ MyMoneyMoney minBalance = forecastBalance(acc, (t_cycle * accountsCycle() + daysToBeginDay));
+ QDate minDate = QDate::currentDate().addDays(t_cycle * accountsCycle() + daysToBeginDay);
+ for(int t_day = 1; t_day <= accountsCycle() ; ++t_day) {
+ if( minBalance > forecastBalance(acc, (t_cycle * accountsCycle()) + daysToBeginDay + t_day) ) {
+ minBalance = forecastBalance(acc, (t_cycle * accountsCycle()) + daysToBeginDay + t_day );
+ minDate = QDate::currentDate().addDays( (t_cycle * accountsCycle()) + daysToBeginDay + t_day);
+ }
+ }
+ minBalanceList.append(minDate);
+ }
+ return minBalanceList;
+}
+
+QValueList<QDate> MyMoneyForecast::accountMaximumBalanceDateList(const MyMoneyAccount& acc)
+{
+ QValueList<QDate> maxBalanceList;
+ int daysToBeginDay;
+
+ daysToBeginDay = QDate::currentDate().daysTo(beginForecastDate());
+
+ for(int t_cycle = 0; ((t_cycle * accountsCycle()) + daysToBeginDay) < forecastDays() ; ++t_cycle) {
+ MyMoneyMoney maxBalance = forecastBalance(acc, ((t_cycle * accountsCycle()) + daysToBeginDay));
+ QDate maxDate = QDate::currentDate().addDays((t_cycle * accountsCycle()) + daysToBeginDay);
+
+ for(int t_day = 0; t_day < accountsCycle() ; ++t_day) {
+ if( maxBalance < forecastBalance(acc, (t_cycle * accountsCycle()) + daysToBeginDay + t_day) ) {
+ maxBalance = forecastBalance(acc, (t_cycle * accountsCycle()) + daysToBeginDay + t_day );
+ maxDate = QDate::currentDate().addDays((t_cycle * accountsCycle()) + daysToBeginDay + t_day);
+ }
+ }
+ maxBalanceList.append(maxDate);
+ }
+ return maxBalanceList;
+}
+
+MyMoneyMoney MyMoneyForecast::accountAverageBalance(const MyMoneyAccount& acc)
+{
+ MyMoneyMoney totalBalance;
+ for(int f_day = 1; f_day <= forecastDays() ; ++f_day) {
+ totalBalance += forecastBalance(acc, f_day);
+ }
+ return totalBalance / MyMoneyMoney( forecastDays(), 1);
+}
+
+int MyMoneyForecast::calculateBeginForecastDay()
+{
+ int fDays = forecastDays();
+ int beginDay = beginForecastDay();
+ int accCycle = accountsCycle();
+ QDate beginDate;
+
+ //if 0, beginDate is current date and forecastDays remains unchanged
+ if(beginDay == 0) {
+ setBeginForecastDate(QDate::currentDate());
+ return fDays;
+ }
+
+ //adjust if beginDay more than days of current month
+ if(QDate::currentDate().daysInMonth() < beginDay)
+ beginDay = QDate::currentDate().daysInMonth();
+
+ //if beginDay still to come, calculate and return
+ if(QDate::currentDate().day() <= beginDay) {
+ beginDate = QDate( QDate::currentDate().year(), QDate::currentDate().month(), beginDay);
+ fDays += QDate::currentDate().daysTo(beginDate);
+ setBeginForecastDate(beginDate);
+ return fDays;
+ }
+
+ //adjust beginDay for next month
+ if(QDate::currentDate().addMonths(1).daysInMonth() < beginDay)
+ beginDay = QDate::currentDate().addMonths(1).daysInMonth();
+
+ //if beginDay of next month comes before 1 interval, use beginDay next month
+ if(QDate::currentDate().addDays(accCycle) >=
+ (QDate(QDate::currentDate().addMonths(1).year(), QDate::currentDate().addMonths(1).month(), 1).addDays(beginDay-1) ) )
+ {
+ beginDate = QDate(QDate::currentDate().addMonths(1).year(), QDate::currentDate().addMonths(1).month(), 1).addDays(beginDay-1);
+ fDays += QDate::currentDate().daysTo(beginDate);
+ }
+ else //add intervals to current beginDay and take the first after current date
+ {
+ beginDay = ((((QDate::currentDate().day()-beginDay)/accCycle) + 1) * accCycle) + beginDay;
+ beginDate = QDate::currentDate().addDays(beginDay - QDate::currentDate().day());
+ fDays += QDate::currentDate().daysTo(beginDate);
+ }
+
+ setBeginForecastDate(beginDate);
+ return fDays;
+}
+
+void MyMoneyForecast::purgeForecastAccountsList(QMap<QString, dailyBalances>& accountList)
+{
+ QMap<QString, QString>::Iterator it_n;
+ for ( it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ) {
+ if(!accountList.contains(*it_n)) {
+ m_nameIdx.remove(it_n);
+ it_n = m_nameIdx.begin();
+ } else
+ ++it_n;
+ }
+}
+
+void MyMoneyForecast::createBudget ( MyMoneyBudget& budget, QDate historyStart, QDate historyEnd, QDate budgetStart, QDate budgetEnd, const bool returnBudget )
+{
+ // clear all data except the id and name
+ QString name = budget.name();
+ budget = MyMoneyBudget(budget.id(), MyMoneyBudget());
+ budget.setName(name);
+
+ //check parameters
+ if ( historyStart > historyEnd ||
+ budgetStart > budgetEnd ||
+ budgetStart <= historyEnd )
+ {
+ throw new MYMONEYEXCEPTION ( "Illegal parameters when trying to create budget" );
+ }
+
+ //get forecast method
+ int fMethod = forecastMethod();
+
+ //set start date to 1st of month and end dates to last day of month, since we deal with full months in budget
+ historyStart = QDate ( historyStart.year(), historyStart.month(), 1 );
+ historyEnd = QDate ( historyEnd.year(), historyEnd.month(), historyEnd.daysInMonth() );
+ budgetStart = QDate ( budgetStart.year(), budgetStart.month(), 1 );
+ budgetEnd = QDate ( budgetEnd.year(), budgetEnd.month(), budgetEnd.daysInMonth() );
+
+ //set forecast parameters
+ setHistoryStartDate ( historyStart );
+ setHistoryEndDate ( historyEnd );
+ setForecastStartDate ( budgetStart );
+ setForecastEndDate ( budgetEnd );
+ setForecastDays ( budgetStart.daysTo ( budgetEnd ) + 1 );
+ if ( budgetStart.daysTo ( budgetEnd ) > historyStart.daysTo ( historyEnd ) ) { //if history period is shorter than budget, use that one as the trend length
+ setAccountsCycle ( historyStart.daysTo ( historyEnd ) ); //we set the accountsCycle to the base timeframe we will use to calculate the average (eg. 180 days, 365, etc)
+ } else { //if one timeframe is larger than the other, but not enough to be 1 time larger, we take the lowest value
+ setAccountsCycle ( budgetStart.daysTo ( budgetEnd ) );
+ }
+ setForecastCycles ( ( historyStart.daysTo ( historyEnd ) / accountsCycle() ) );
+ if ( forecastCycles() == 0 ) //the cycles must be at least 1
+ setForecastCycles ( 1 );
+
+ //do not skip opening date
+ setSkipOpeningDate ( false );
+
+ //clear and set accounts list we are going to use. Categories, in this case
+ m_nameIdx.clear();
+ setBudgetAccountList();
+
+ //calculate budget according to forecast method
+ switch(fMethod)
+ {
+ case eScheduled:
+ doFutureScheduledForecast();
+ calculateScheduledMonthlyBalances();
+ break;
+ case eHistoric:
+ pastTransactions(); //get all transactions for history period
+ calculateAccountTrendList();
+ calculateHistoricMonthlyBalances(); //add all balances of each month and put at the 1st day of each month
+ break;
+ default:
+ break;
+ }
+
+ //flag the forecast as done
+ m_forecastDone = true;
+
+ //only fill the budget if it is going to be used
+ if ( returnBudget ) {
+ //setup the budget itself
+ MyMoneyFile* file = MyMoneyFile::instance();
+ budget.setBudgetStart ( budgetStart );
+
+ //go through all the accounts and add them to budget
+ QMap<QString, QString>::ConstIterator it_nc;
+ for ( it_nc = m_nameIdx.begin(); it_nc != m_nameIdx.end(); ++it_nc ) {
+ MyMoneyAccount acc = file->account ( *it_nc );
+
+ MyMoneyBudget::AccountGroup budgetAcc;
+ budgetAcc.setId ( acc.id() );
+ budgetAcc.setBudgetLevel ( MyMoneyBudget::AccountGroup::eMonthByMonth );
+
+ for ( QDate f_date = forecastStartDate(); f_date <= forecastEndDate(); ) {
+ MyMoneyBudget::PeriodGroup period;
+
+ //add period to budget account
+ period.setStartDate ( f_date );
+ period.setAmount ( forecastBalance ( acc, f_date ) );
+ budgetAcc.addPeriod ( f_date, period );
+
+ //next month
+ f_date = f_date.addMonths ( 1 );
+ }
+ //add budget account to budget
+ budget.setAccount ( budgetAcc, acc.id() );
+ }
+ }
+}
+
+void MyMoneyForecast::setBudgetAccountList(void)
+{
+ //get budget accounts
+ QValueList<MyMoneyAccount> accList;
+ accList = budgetAccountList();
+
+ QValueList<MyMoneyAccount>::const_iterator accList_t = accList.begin();
+ for(; accList_t != accList.end(); ++accList_t ) {
+ MyMoneyAccount acc = *accList_t;
+ // check if this is a new account for us
+ if(m_nameIdx[acc.id()] != acc.id()) {
+ m_nameIdx[acc.id()] = acc.id();
+ }
+ }
+}
+
+QValueList<MyMoneyAccount> MyMoneyForecast::budgetAccountList(void)
+{
+ MyMoneyFile* file = MyMoneyFile::instance();
+
+ QValueList<MyMoneyAccount> accList;
+ QStringList emptyStringList;
+ //Get all accounts from the file and check if they are of the right type to calculate forecast
+ file->accountList(accList, emptyStringList, false);
+ QValueList<MyMoneyAccount>::iterator accList_t = accList.begin();
+ for(; accList_t != accList.end(); ) {
+ MyMoneyAccount acc = *accList_t;
+ if(acc.isClosed() //check the account is not closed
+ || (!acc.isIncomeExpense()) ) {
+ accList.remove(accList_t); //remove the account if it is not of the correct type
+ accList_t = accList.begin();
+ } else {
+ ++accList_t;
+ }
+ }
+ return accList;
+}
+
+void MyMoneyForecast::calculateHistoricMonthlyBalances()
+{
+ MyMoneyFile* file = MyMoneyFile::instance();
+
+ //Calculate account monthly balances
+ QMap<QString, QString>::ConstIterator it_n;
+ for(it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
+ MyMoneyAccount acc = file->account(*it_n);
+
+ for( QDate f_date = forecastStartDate(); f_date <= forecastEndDate(); ) {
+ for(int f_day = 1; f_day <= accountsCycle() && f_date <= forecastEndDate(); ++f_day) {
+ MyMoneyMoney accountDailyTrend = m_accountTrendList[acc.id()][f_day]; //trend for that day
+ //check for leap year
+ if(f_date.month() == 2 && f_date.day() == 29)
+ f_date = f_date.addDays(1); //skip 1 day
+ m_accountList[acc.id()][QDate(f_date.year(), f_date.month(), 1)] += accountDailyTrend; //movement trend for that particular day
+ f_date = f_date.addDays(1);
+ }
+ }
+ }
+}
+
+void MyMoneyForecast::calculateScheduledMonthlyBalances()
+{
+ MyMoneyFile* file = MyMoneyFile::instance();
+
+//Calculate account monthly balances
+ QMap<QString, QString>::ConstIterator it_n;
+ for(it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
+ MyMoneyAccount acc = file->account(*it_n);
+
+ for( QDate f_date = forecastStartDate(); f_date <= forecastEndDate(); f_date = f_date.addDays(1) ) {
+ //get the trend for the day
+ MyMoneyMoney accountDailyBalance = m_accountList[acc.id()][f_date];
+
+ //do not add if it is the beginning of the month
+ //otherwise we end up with duplicated values as reported by Marko Käning
+ if(f_date != QDate(f_date.year(), f_date.month(), 1) )
+ m_accountList[acc.id()][QDate(f_date.year(), f_date.month(), 1)] += accountDailyBalance;
+ }
+ }
+}
+
+void MyMoneyForecast::setStartingBalance(const MyMoneyAccount &acc)
+{
+ MyMoneyFile* file = MyMoneyFile::instance();
+
+ //Get current account balance
+ if ( acc.isInvest() ) { //investments require special treatment
+ //get the security id of that account
+ MyMoneySecurity undersecurity = file->security ( acc.currencyId() );
+
+ //only do it if the security is not an actual currency
+ if ( ! undersecurity.isCurrency() )
+ {
+ //set the default value
+ MyMoneyMoney rate = MyMoneyMoney ( 1, 1 );
+ //get te
+ MyMoneyPrice price = file->price ( undersecurity.id(), undersecurity.tradingCurrency(), QDate::currentDate() );
+ if ( price.isValid() )
+ {
+ rate = price.rate ( undersecurity.tradingCurrency() );
+ }
+ m_accountList[acc.id()][QDate::currentDate()] = file->balance(acc.id(), QDate::currentDate()) * rate;
+ }
+ } else {
+ m_accountList[acc.id()][QDate::currentDate()] = file->balance(acc.id(), QDate::currentDate());
+ }
+
+ //if the method is linear regression, we have to add the opening balance to m_accountListPast
+ if(forecastMethod() == eHistoric && historyMethod() == 2) {
+ //FIXME workaround for stock opening dates
+ QDate openingDate;
+ if(acc.accountType() == MyMoneyAccount::Stock) {
+ MyMoneyAccount parentAccount = file->account(acc.parentAccountId());
+ openingDate = parentAccount.openingDate();
+ } else {
+ openingDate = acc.openingDate();
+ }
+
+ //add opening balance only if it opened after the history start
+ if(openingDate >= historyStartDate()) {
+
+ MyMoneyMoney openingBalance;
+
+ openingBalance = file->balance(acc.id(), openingDate);
+
+ //calculate running sum
+ for(QDate it_date = openingDate; it_date <= historyEndDate(); it_date = it_date.addDays(1) ) {
+ //investments require special treatment
+ if ( acc.isInvest() ) {
+ //get the security id of that account
+ MyMoneySecurity undersecurity = file->security ( acc.currencyId() );
+
+ //only do it if the security is not an actual currency
+ if ( ! undersecurity.isCurrency() )
+ {
+ //set the default value
+ MyMoneyMoney rate = MyMoneyMoney ( 1, 1 );
+
+ //get the rate for that specific date
+ MyMoneyPrice price = file->price ( undersecurity.id(), undersecurity.tradingCurrency(), it_date );
+ if ( price.isValid() )
+ {
+ rate = price.rate ( undersecurity.tradingCurrency() );
+ }
+ m_accountListPast[acc.id()][it_date] += openingBalance * rate;
+ }
+ } else {
+ m_accountListPast[acc.id()][it_date] += openingBalance;
+ }
+ }
+ }
+ }
+}
+
+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);
+ }
+ }
+}
+
diff --git a/kmymoney2/mymoney/mymoneyforecast.h b/kmymoney2/mymoney/mymoneyforecast.h
new file mode 100644
index 0000000..f69e596
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyforecast.h
@@ -0,0 +1,408 @@
+/***************************************************************************
+ mymoneyforecast.h
+ -------------------
+ begin : Wed May 30 2007
+ copyright : (C) 2007 by Alvaro Soliverez
+ email : asoliverez@gmail.com
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYFORECAST_H
+#define MYMONEYFORECAST_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qmap.h>
+#include <qvaluelist.h>
+#include <qstring.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include <kmymoney/mymoneyobject.h>
+#include <kmymoney/mymoneyaccount.h>
+#include <kmymoney/mymoneymoney.h>
+#include <kmymoney/mymoneyscheduled.h>
+#include <kmymoney/export.h>
+#include "mymoneybudget.h"
+
+/**
+ *
+ *
+ * @author Alvaro Soliverez <asoliverez@gmail.com>
+ */
+
+class MyMoneyForecast
+{
+public:
+ MyMoneyForecast();
+ ~MyMoneyForecast();
+
+ /**
+ * calculate forecast based on historic transactions
+ */
+ void doForecast();
+
+ /**
+ * Returns the list of accounts to be forecast.
+ */
+ QValueList<MyMoneyAccount> accountList(void);
+
+ /**
+ * Returns the balance trend for account @a acc based on a number of days @p forecastDays
+ * Collects and processes all transactions in the past for the
+ * same period of forecast and calculates the balance trend
+ */
+ static MyMoneyMoney calculateAccountTrend(const MyMoneyAccount& acc, int forecastDays);
+
+ /**
+ * Returns the forecast balance trend for account @a acc for day @p QDate
+ */
+ MyMoneyMoney forecastBalance(const MyMoneyAccount& acc, QDate forecastDate);
+
+ /**
+ * Returns the forecast balance trend for account @a acc for offset @p int
+ * offset is days from current date, inside forecast days.
+ * Returns 0 if offset not in range of forecast days.
+ */
+ MyMoneyMoney forecastBalance(const MyMoneyAccount& acc, int offset);
+
+ /**
+ * Returns true if an account @a acc is an account to be forecast
+ */
+ bool isForecastAccount(const MyMoneyAccount& acc);
+
+ /**
+ * returns the number of days when a given account is forecast to be below minimum balance
+ * returns -1 if it will not be below minimum balance in the forecast period
+ */
+ int daysToMinimumBalance(const MyMoneyAccount& acc);
+
+ /**
+ * returns the number of days when a given account is forecast to be below zero if it is an asset accounts
+ * or above zero if it is a liability account
+ * returns -1 if it will not happen in the forecast period
+ */
+ int daysToZeroBalance(const MyMoneyAccount& acc);
+
+ /**
+ * amount of variation of a given account in one cycle
+ */
+ MyMoneyMoney accountCycleVariation(const MyMoneyAccount& acc);
+
+ /**
+ * amount of variation of a given account for the whole forecast period
+ */
+ MyMoneyMoney accountTotalVariation(const MyMoneyAccount& acc);
+
+ /**
+ * returns a list of the dates where the account was on its lowest balance in each cycle
+ */
+ QValueList<QDate> accountMinimumBalanceDateList(const MyMoneyAccount& acc);
+
+ /**
+ * returns a list of the dates where the account was on its highest balance in each cycle
+ */
+ QValueList<QDate> accountMaximumBalanceDateList(const MyMoneyAccount& acc);
+
+ /**
+ * returns the average balance of the account within the forecast period
+ */
+ MyMoneyMoney accountAverageBalance(const MyMoneyAccount& acc);
+
+ /**
+ * creates a budget based on the history of a given timeframe
+ */
+ void createBudget(MyMoneyBudget& budget, QDate historyStart, QDate historyEnd, QDate budgetStart, QDate budgetEnd, const bool returnBudget);
+
+ /**
+ * number of days to go back in history to calculate forecast
+ */
+ int historyDays(void) const { return (m_historyStartDate.daysTo(m_historyEndDate) + 1); }
+
+ void setAccountsCycle(int accountsCycle) { m_accountsCycle = accountsCycle; }
+ void setForecastCycles(int forecastCycles) { m_forecastCycles = forecastCycles; }
+ void setForecastDays(int forecastDays) { m_forecastDays = forecastDays; }
+ void setBeginForecastDate(QDate beginForecastDate) { m_beginForecastDate = beginForecastDate; }
+ void setBeginForecastDay(int beginDay) { m_beginForecastDay = beginDay; }
+ void setForecastMethod(int forecastMethod) { m_forecastMethod = forecastMethod; }
+ void setHistoryStartDate(QDate historyStartDate) { m_historyStartDate = historyStartDate; }
+ void setHistoryEndDate(QDate historyEndDate) { m_historyEndDate = historyEndDate; }
+ void setHistoryStartDate(int daysToStartDate) { setHistoryStartDate(QDate::currentDate().addDays(-daysToStartDate)); }
+ void setHistoryEndDate(int daysToEndDate) { setHistoryEndDate(QDate::currentDate().addDays(-daysToEndDate)); }
+ void setForecastStartDate(QDate _startDate) { m_forecastStartDate = _startDate; }
+ void setForecastEndDate(QDate _endDate) { m_forecastEndDate = _endDate; }
+ void setSkipOpeningDate(bool _skip) { m_skipOpeningDate = _skip; }
+ void setHistoryMethod(int historyMethod) { m_historyMethod = historyMethod; }
+ void setIncludeUnusedAccounts(bool _bool) { m_includeUnusedAccounts = _bool; }
+ void setForecastDone(bool _bool) { m_forecastDone = _bool; }
+ void setIncludeFutureTransactions(bool _bool) { m_includeFutureTransactions = _bool; }
+ void setIncludeScheduledTransactions(bool _bool) { m_includeScheduledTransactions = _bool; }
+
+ int accountsCycle(void) const { return m_accountsCycle; }
+ int forecastCycles(void) const { return m_forecastCycles; }
+ int forecastDays(void) const { return m_forecastDays; }
+ QDate beginForecastDate(void) const { return m_beginForecastDate; }
+ int beginForecastDay(void) const { return m_beginForecastDay; }
+ int forecastMethod(void) const { return m_forecastMethod; }
+ QDate historyStartDate(void) const { return m_historyStartDate; }
+ QDate historyEndDate(void) const { return m_historyEndDate; }
+ QDate forecastStartDate(void) const { return m_forecastStartDate; }
+ QDate forecastEndDate(void) const { return m_forecastEndDate; }
+ bool skipOpeningDate(void) const { return m_skipOpeningDate; }
+ int historyMethod(void) const { return m_historyMethod; }
+ bool isIncludingUnusedAccounts(void) const { return m_includeUnusedAccounts; }
+ bool isForecastDone(void) const { return m_forecastDone; }
+ bool isIncludingFutureTransactions(void) const { return m_includeFutureTransactions; }
+ bool isIncludingScheduledTransactions(void) const { return m_includeScheduledTransactions; }
+
+ /**
+ * This method modifies a scheduled loan transaction such that all
+ * references to automatic calculated values are resolved to actual values.
+ *
+ * @param schedule const reference to the schedule the transaction is based on
+ * @param transaction reference to the transaction to be checked and modified
+ * @param balances QMap of (account-id,balance) pairs to be used as current balance
+ * for the calculation of interest. If map is empty, the engine
+ * will be interrogated for current balances.
+ */
+ static void calculateAutoLoan(const MyMoneySchedule& schedule, MyMoneyTransaction& transaction, const QMap<QString, MyMoneyMoney>& balances);
+
+private:
+
+ enum EForecastMethod {eScheduled = 0, eHistoric = 1 };
+
+ /**
+ * daily balances of an account
+ */
+ typedef QMap<QDate, MyMoneyMoney> dailyBalances;
+
+ /**
+ * map of trends of an account
+ */
+ typedef QMap<int, MyMoneyMoney> trendBalances;
+
+ /**
+ * Returns the list of accounts to be forecast. Only Asset and Liability are returned.
+ */
+ static QValueList<MyMoneyAccount> forecastAccountList(void);
+
+ /**
+ * Returns the list of accounts to create a budget. Only Income and Expenses are returned.
+ */
+ QValueList<MyMoneyAccount> budgetAccountList(void);
+
+ /**
+ * calculate daily forecast balance based on historic transactions
+ */
+ void calculateHistoricDailyBalances(void);
+
+ /**
+ * calculate monthly budget balance based on historic transactions
+ */
+ void calculateHistoricMonthlyBalances();
+
+ /**
+ * calculate monthly budget balance based on historic transactions
+ */
+ void calculateScheduledMonthlyBalances();
+
+ /**
+ * calculate forecast based on future and scheduled transactions
+ */
+ void doFutureScheduledForecast(void);
+
+ /**
+ * add future transactions to forecast
+ */
+ void addFutureTransactions(void);
+
+ /**
+ * add scheduled transactions to forecast
+ */
+ void addScheduledTransactions (void);
+
+ /**
+ * calculate daily forecast balance based on future and scheduled transactions
+ */
+ void calculateScheduledDailyBalances(void);
+
+ /**
+ * set the starting balance for an accounts
+ */
+ void setStartingBalance(const MyMoneyAccount& acc);
+
+ /**
+ * Returns the day moving average for the account @a acc based on the daily balances of a given number of @p forecastTerms
+ * It returns the moving average for a given @p trendDay of the forecastTerm
+ * With a term of 1 month and 3 terms, it calculates the trend taking the transactions occured
+ * at that day and the day before,for the last 3 months
+ */
+ MyMoneyMoney accountMovingAverage(const MyMoneyAccount& acc, const int trendDay, const int forecastTerms);
+
+ /**
+ * Returns the weighted moving average for a given @p trendDay
+ */
+ MyMoneyMoney accountWeightedMovingAverage(const MyMoneyAccount& acc, const int trendDay, const int totalWeight);
+
+ /**
+ * Returns the linear regression for a given @p trendDay
+ */
+ MyMoneyMoney accountLinearRegression(const MyMoneyAccount &acc, const int trendDay, const int totalWeight, const MyMoneyMoney meanTerms);
+
+ /**
+ * calculate daily forecast trend based on historic transactions
+ */
+ void calculateAccountTrendList(void);
+
+ /**
+ * set the internal list of accounts to be forecast
+ */
+ void setForecastAccountList(void);
+
+ /**
+ * set the internal list of accounts to create a budget
+ */
+ void setBudgetAccountList(void);
+
+ /**
+ * get past transactions for the accounts to be forecast
+ */
+ void pastTransactions(void);
+
+ /**
+ * calculate the day to start forecast and sets the begin date
+ * The quantity of forecast days will be counted from this date
+ * Depends on the values of begin day and accounts cycle
+ * The rules to calculate begin day are as follows:
+ * - if beginDay is 0, begin date is current date
+ * - if the day of the month set by beginDay has not passed, that will be used
+ * - if adding an account cycle to beginDay, will not go past the beginDay of next month,
+ * that date will be used, otherwise it will add account cycle to beginDay until it is past current date
+ * It returns the total amount of Forecast Days from current date.
+ */
+ int calculateBeginForecastDay();
+
+ /**
+ * remove accounts from the list if the accounts has no transactions in the forecast timeframe.
+ * Used for scheduled-forecast method.
+ */
+ void purgeForecastAccountsList(QMap<QString, dailyBalances>& accountList);
+
+ /**
+ * daily forecast balance of accounts
+ */
+ QMap<QString, dailyBalances> m_accountList;
+
+ /**
+ * daily past balance of accounts
+ */
+ QMap<QString, dailyBalances> m_accountListPast;
+
+ /**
+ * daily forecast trends of accounts
+ */
+ QMap<QString, trendBalances> m_accountTrendList;
+
+ /**
+ * list of forecast accounts
+ */
+ QMap<QString, QString> m_nameIdx;
+
+ /**
+ * cycle of accounts in days
+ */
+ int m_accountsCycle;
+
+ /**
+ * number of cycles to use in forecast
+ */
+ int m_forecastCycles;
+
+ /**
+ * number of days to forecast
+ */
+ int m_forecastDays;
+
+ /**
+ * date to start forecast
+ */
+ QDate m_beginForecastDate;
+
+ /**
+ * day to start forecast
+ */
+ int m_beginForecastDay;
+
+ /**
+ * forecast method
+ */
+ int m_forecastMethod;
+
+ /**
+ * history method
+ */
+ int m_historyMethod;
+
+ /**
+ * start date of history
+ */
+ QDate m_historyStartDate;
+
+ /**
+ * end date of history
+ */
+ QDate m_historyEndDate;
+
+ /**
+ * start date of forecast
+ */
+ QDate m_forecastStartDate;
+
+ /**
+ * end date of forecast
+ */
+ QDate m_forecastEndDate;
+
+ /**
+ * skip opening date when fetching transactions of an account
+ */
+ bool m_skipOpeningDate;
+
+ /**
+ * include accounts with no transactions in the forecast timeframe. default is false.
+ */
+ bool m_includeUnusedAccounts;
+
+ /**
+ * forecast already done
+ */
+ bool m_forecastDone;
+
+ /**
+ * include future transactions when doing a scheduled-based forecast
+ */
+ bool m_includeFutureTransactions;
+
+ /**
+ * include scheduled transactions when doing a scheduled-based forecast
+ */
+ bool m_includeScheduledTransactions;
+
+};
+
+#endif // MYMONEYFORECAST_H
+
diff --git a/kmymoney2/mymoney/mymoneyforecasttest.cpp b/kmymoney2/mymoney/mymoneyforecasttest.cpp
new file mode 100644
index 0000000..dbe96ac
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyforecasttest.cpp
@@ -0,0 +1,977 @@
+/***************************************************************************
+ mymoneyforecasttest.cpp
+ -------------------
+ copyright : (C) 2007 by Alvaro Soliverez
+ email : asoliverez@gmail.com
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "mymoneyforecasttest.h"
+
+#include <iostream>
+#include <qvaluelist.h>
+
+#include "mymoneybudget.h"
+
+#include <kmymoney/mymoneyexception.h>
+
+#include "../kmymoneyglobalsettings.h"
+#include "../mymoney/storage/mymoneystoragedump.h"
+#include "../mymoney/storage/mymoneystoragexml.h"
+#include "../reports/reportstestcommon.h"
+
+
+using namespace test;
+
+MyMoneyForecastTest::MyMoneyForecastTest()
+{
+ this->moT1 = MyMoneyMoney(57,1);
+ this->moT2 = MyMoneyMoney(63,1);
+ this->moT3 = MyMoneyMoney(84,1);
+ this->moT4 = MyMoneyMoney(62,1);
+ this->moT5 = MyMoneyMoney(104,1);
+}
+
+
+void MyMoneyForecastTest::setUp () {
+
+ //all this has been taken from pivottabletest.cpp, by Thomas Baumgart and Ace Jones
+
+ storage = new MyMoneySeqAccessMgr;
+ file = MyMoneyFile::instance();
+ file->attachStorage(storage);
+
+ MyMoneyFileTransaction ft;
+ file->addCurrency(MyMoneySecurity("CAD", "Canadian Dollar", "C$"));
+ file->addCurrency(MyMoneySecurity("USD", "US Dollar", "$"));
+ file->addCurrency(MyMoneySecurity("JPY", "Japanese Yen", QChar(0x00A5), 100, 1));
+ file->addCurrency(MyMoneySecurity("GBP", "British Pound", "#"));
+ file->setBaseCurrency(file->currency("USD"));
+
+ MyMoneyPayee payeeTest("Test Payee");
+ file->addPayee(payeeTest);
+ MyMoneyPayee payeeTest2("Alvaro Soliverez");
+ file->addPayee(payeeTest2);
+
+ acAsset = (MyMoneyFile::instance()->asset().id());
+ acLiability = (MyMoneyFile::instance()->liability().id());
+ acExpense = (MyMoneyFile::instance()->expense().id());
+ acIncome = (MyMoneyFile::instance()->income().id());
+ acChecking = makeAccount(QString("Checking Account"),MyMoneyAccount::Checkings,moCheckingOpen,QDate(2004,5,15),acAsset, "USD");
+ acCredit = makeAccount(QString("Credit Card"),MyMoneyAccount::CreditCard,moCreditOpen,QDate(2004,7,15),acLiability, "USD");
+ acSolo = makeAccount(QString("Solo"),MyMoneyAccount::Expense,0,QDate(2004,1,11),acExpense, "USD");
+ acParent = makeAccount(QString("Parent"),MyMoneyAccount::Expense,0,QDate(2004,1,11),acExpense, "USD");
+ acChild = makeAccount(QString("Child"),MyMoneyAccount::Expense,0,QDate(2004,2,11),acParent, "USD");
+ acForeign = makeAccount(QString("Foreign"),MyMoneyAccount::Expense,0,QDate(2004,1,11),acExpense, "USD");
+ acInvestment = makeAccount("Investment",MyMoneyAccount::Investment,moZero,QDate(2004,1,1),acAsset, "USD");
+
+ acSecondChild = makeAccount(QString("Second Child"),MyMoneyAccount::Expense,0,QDate(2004,2,11),acParent, "USD");
+ acGrandChild1 = makeAccount(QString("Grand Child 1"),MyMoneyAccount::Expense,0,QDate(2004,2,11),acChild, "USD");
+ acGrandChild2 = makeAccount(QString("Grand Child 2"),MyMoneyAccount::Expense,0,QDate(2004,2,11),acChild, "USD");
+
+ //this account added to have an account to test opening date calculations
+ acCash = makeAccount(QString("Cash"),MyMoneyAccount::Cash,moCreditOpen,QDate::currentDate().addDays(-2),acAsset, "USD");
+
+
+ MyMoneyInstitution i("Bank of the World","","","","","","");
+ file->addInstitution(i);
+ inBank = i.id();
+ ft.commit();
+
+}
+
+void MyMoneyForecastTest::tearDown () {
+ file->detachStorage(storage);
+ delete storage;
+}
+
+void MyMoneyForecastTest::testEmptyConstructor() {
+ MyMoneyForecast a;
+ MyMoneyAccount b;
+
+ int f = a.forecastBalance(b, QDate::currentDate());
+
+ CPPUNIT_ASSERT(f == 0);
+ CPPUNIT_ASSERT(!a.isForecastAccount(b));
+ CPPUNIT_ASSERT(a.forecastBalance(b, QDate::currentDate()) == MyMoneyMoney(0,1));
+ CPPUNIT_ASSERT(a.daysToMinimumBalance(b) == -1);
+ CPPUNIT_ASSERT(a.daysToZeroBalance(b) == -2);
+ CPPUNIT_ASSERT(a.forecastDays() == KMyMoneyGlobalSettings::forecastDays());
+ CPPUNIT_ASSERT(a.accountsCycle() == KMyMoneyGlobalSettings::forecastAccountCycle());
+ CPPUNIT_ASSERT(a.forecastCycles() == KMyMoneyGlobalSettings::forecastCycles());
+ CPPUNIT_ASSERT(a.historyStartDate() == QDate::currentDate().addDays(-KMyMoneyGlobalSettings::forecastCycles()*KMyMoneyGlobalSettings::forecastAccountCycle()));
+ CPPUNIT_ASSERT(a.historyEndDate() == QDate::currentDate().addDays(-1));
+ CPPUNIT_ASSERT(a.historyDays() == KMyMoneyGlobalSettings::forecastAccountCycle() * KMyMoneyGlobalSettings::forecastCycles());
+}
+
+
+void MyMoneyForecastTest::testDoForecastInit() {
+ MyMoneyForecast a;
+
+ a.doForecast();
+ /*
+ //check the illegal argument validation
+ try {
+ KMyMoneyGlobalSettings::setForecastDays(-10);
+ a.doForecast();
+ }
+ catch (MyMoneyException *e)
+ {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+ try {
+ KMyMoneyGlobalSettings::setForecastAccountCycle(-20);
+ a.doForecast();
+ }
+ catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+ try {
+ KMyMoneyGlobalSettings::setForecastCycles(-10);
+ a.doForecast();
+ }
+ catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ try {
+ KMyMoneyGlobalSettings::setForecastAccountCycle(0);
+ a.doForecast();
+ }
+ catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+ try {
+ KMyMoneyGlobalSettings::setForecastDays(0);
+ KMyMoneyGlobalSettings::setForecastCycles(0);
+ KMyMoneyGlobalSettings::setForecastAccountCycle(0);
+ a.doForecast();
+ }
+ catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_ASSERT("Unexpected exception");
+ }*/
+}
+
+//test that it forecasts correctly with transactions in the period of forecast
+void MyMoneyForecastTest::testDoForecast() {
+ //set up environment
+ MyMoneyForecast a;
+
+ MyMoneyAccount a_checking = file->account(acChecking);
+ MyMoneyAccount a_credit = file->account(acCredit);
+
+ //test empty forecast
+ a.doForecast(); //this is just to check nothing goes wrong if forecast is run agains an empty template
+
+ //setup some transactions
+ TransactionHelper t1( QDate::currentDate().addDays(-1), MyMoneySplit::ActionWithdrawal, this->moT1, acChecking, acSolo);
+ TransactionHelper t2( QDate::currentDate().addDays(-1), MyMoneySplit::ActionDeposit, -(this->moT2), acCredit, acParent);
+ TransactionHelper t3( QDate::currentDate().addDays(-1), MyMoneySplit::ActionTransfer, this->moT1, acCredit, acChecking);
+
+ a.setForecastMethod(1);
+ a.setForecastDays(3);
+ a.setAccountsCycle(1);
+ a.setForecastCycles(1);
+ a.setBeginForecastDay(0);
+ a.setHistoryMethod(0); //moving average
+ a.doForecast();
+
+ //checking didn't have balance variations, so the forecast should be equal to the current balance
+ MyMoneyMoney b_checking = file->balance(a_checking.id(), QDate::currentDate());
+
+ CPPUNIT_ASSERT(a.forecastBalance(a_checking, QDate::currentDate().addDays(1))==b_checking);
+ CPPUNIT_ASSERT(a.forecastBalance(a_checking, QDate::currentDate().addDays(2))==b_checking);
+ CPPUNIT_ASSERT(a.forecastBalance(a_checking, QDate::currentDate().addDays(3))==b_checking);
+ CPPUNIT_ASSERT(a.forecastBalance(a_checking, QDate::currentDate())==b_checking);
+ //credit had a variation so the forecast should be different for each day
+ MyMoneyMoney b_credit = file->balance(a_credit.id(), QDate::currentDate());
+
+ CPPUNIT_ASSERT(a.forecastBalance(a_credit, 0) == b_credit);
+ CPPUNIT_ASSERT(a.forecastBalance(a_credit, QDate::currentDate().addDays(1)) == (b_credit+(moT2-moT1)));
+ CPPUNIT_ASSERT(a.forecastBalance(a_credit, QDate::currentDate().addDays(2)) == (b_credit+((moT2-moT1)*2)));
+ CPPUNIT_ASSERT(a.forecastBalance(a_credit, QDate::currentDate().addDays(3)) == b_credit+((moT2-moT1)*3));
+
+ a.setHistoryMethod(1); //weighted moving average
+ a.doForecast();
+
+ CPPUNIT_ASSERT(a.forecastBalance(a_credit, 0) == b_credit);
+ CPPUNIT_ASSERT(a.forecastBalance(a_credit, QDate::currentDate().addDays(1)) == (b_credit+(moT2-moT1)));
+ CPPUNIT_ASSERT(a.forecastBalance(a_credit, QDate::currentDate().addDays(2)) == (b_credit+((moT2-moT1)*2)));
+ CPPUNIT_ASSERT(a.forecastBalance(a_credit, QDate::currentDate().addDays(3)) == b_credit+((moT2-moT1)*3));
+
+ //insert transactions outside the forecast period. The calculation should be the same.
+ TransactionHelper t4( QDate::currentDate().addDays(-2), MyMoneySplit::ActionDeposit, -moT2, acCredit, acParent );
+ TransactionHelper t5( QDate::currentDate().addDays(-10), MyMoneySplit::ActionDeposit, -moT2, acCredit, acParent );
+ TransactionHelper t6( QDate::currentDate().addDays(-3), MyMoneySplit::ActionDeposit, -moT2, acCredit, acParent );
+
+ a.setForecastMethod(1);
+ a.setForecastDays(3);
+ a.setAccountsCycle(1);
+ a.setForecastCycles(1);
+ a.setBeginForecastDay(0);
+ a.setHistoryMethod(0); //moving average
+ a.doForecast();
+ //check forecast
+ b_credit = file->balance(a_credit.id(), QDate::currentDate());
+ MyMoneyMoney b_credit_1_exp = (b_credit+((moT2-moT1)));
+ MyMoneyMoney b_credit_2 = a.forecastBalance(a_credit, QDate::currentDate().addDays(2));
+ MyMoneyMoney b_credit_2_exp = (b_credit+((moT2-moT1)*2));
+ CPPUNIT_ASSERT(a.forecastBalance(a_credit, QDate::currentDate())==file->balance(a_credit.id(), QDate::currentDate()));
+ CPPUNIT_ASSERT(a.forecastBalance(a_credit, QDate::currentDate().addDays(1))==b_credit+(moT2-moT1));
+ CPPUNIT_ASSERT(a.forecastBalance(a_credit, QDate::currentDate().addDays(2))==b_credit+((moT2-moT1)*2));
+ CPPUNIT_ASSERT(a.forecastBalance(a_credit, QDate::currentDate().addDays(3))==b_credit+((moT2-moT1)*3));
+
+ //test weighted moving average
+ a.setForecastMethod(1);
+ a.setForecastDays(3);
+ a.setAccountsCycle(1);
+ a.setForecastCycles(3);
+ a.setBeginForecastDay(0);
+ a.setHistoryMethod(1);
+ a.doForecast();
+
+ CPPUNIT_ASSERT(a.forecastBalance(a_credit, 0) == b_credit);
+ CPPUNIT_ASSERT(a.forecastBalance(a_credit, QDate::currentDate().addDays(1)) == (b_credit+(((moT2-moT1)*3+moT2*2+moT2)/MyMoneyMoney(6,1))));
+
+}
+
+void MyMoneyForecastTest::testGetForecastAccountList()
+{
+ MyMoneyForecast a;
+ MyMoneyAccount a_checking = file->account(acChecking);
+ MyMoneyAccount a_parent = file->account(acParent);
+ QValueList<MyMoneyAccount> b;
+
+ b = a.forecastAccountList();
+ //check that it contains asset account, but not expense accounts
+ CPPUNIT_ASSERT(b.contains(a_checking));
+ CPPUNIT_ASSERT(!b.contains(a_parent));
+
+}
+
+void MyMoneyForecastTest::testCalculateAccountTrend()
+{
+ //set up environment
+ TransactionHelper t1( QDate::currentDate().addDays(-3), MyMoneySplit::ActionDeposit, -moT2, acChecking, acSolo );
+ MyMoneyAccount a_checking = file->account(acChecking);
+
+ //test invalid arguments
+
+ try {
+ MyMoneyForecast::calculateAccountTrend(a_checking, 0);
+ }
+ catch (MyMoneyException *e) {
+ CPPUNIT_ASSERT(e->what().compare("Illegal arguments when calling calculateAccountTrend. trendDays must be higher than 0") == 0);
+ delete e;
+ }
+ try {
+ MyMoneyForecast::calculateAccountTrend(a_checking, -10);
+ }
+ catch (MyMoneyException *e) {
+ CPPUNIT_ASSERT(e->what().compare("Illegal arguments when calling calculateAccountTrend. trendDays must be higher than 0") == 0);
+ delete e;
+ }
+
+ //test that it calculates correctly
+ CPPUNIT_ASSERT(MyMoneyForecast::calculateAccountTrend(a_checking ,3) == moT2/MyMoneyMoney(3,1));
+
+ //test that it works for all kind of accounts
+ MyMoneyAccount a_solo = file->account(acSolo);
+ MyMoneyMoney soloTrend = MyMoneyForecast::calculateAccountTrend(a_solo,3);
+ MyMoneyMoney soloTrendExp = -moT2/MyMoneyMoney(3,1);
+ CPPUNIT_ASSERT(MyMoneyForecast::calculateAccountTrend(a_solo,3) == -moT2/MyMoneyMoney(3,1));
+
+ //test that it does not take into account the transactions of the opening date of the account
+ MyMoneyAccount a_cash = file->account(acCash);
+ TransactionHelper t2( QDate::currentDate().addDays(-2), MyMoneySplit::ActionDeposit, moT2, acCash, acParent );
+ TransactionHelper t3( QDate::currentDate().addDays(-1), MyMoneySplit::ActionDeposit, moT1, acCash, acParent );
+ CPPUNIT_ASSERT(MyMoneyForecast::calculateAccountTrend(a_cash,3) == -moT1);
+
+}
+
+void MyMoneyForecastTest::testGetForecastBalance()
+{
+ //set up environment
+ MyMoneyForecast a;
+
+ TransactionHelper t1( QDate::currentDate().addDays(-1), MyMoneySplit::ActionWithdrawal, this->moT1, acChecking, acSolo);
+ TransactionHelper t2( QDate::currentDate().addDays(-1), MyMoneySplit::ActionDeposit, -(this->moT2), acCredit, acParent);
+ TransactionHelper t3( QDate::currentDate().addDays(-1), MyMoneySplit::ActionTransfer, this->moT1, acCredit, acChecking);
+
+ a.setForecastMethod(1);
+ a.setForecastDays(3);
+ a.setAccountsCycle(1);
+ a.setForecastCycles(1);
+ a.setHistoryMethod(0);
+ a.doForecast();
+
+ MyMoneyAccount a_checking = file->account(acChecking);
+ MyMoneyAccount a_credit = file->account(acCredit);
+
+ //test invalid arguments
+ CPPUNIT_ASSERT(a.forecastBalance(a_checking, QDate::currentDate().addDays(-1))==MyMoneyMoney(0,1));
+ CPPUNIT_ASSERT(a.forecastBalance(a_checking, QDate::currentDate().addDays(-10))==MyMoneyMoney(0,1));
+ CPPUNIT_ASSERT(a.forecastBalance(a_checking, -1)==MyMoneyMoney(0,1));
+ CPPUNIT_ASSERT(a.forecastBalance(a_checking, -100)==MyMoneyMoney(0,1));
+
+ //test a date outside the forecast days
+ CPPUNIT_ASSERT(a.forecastBalance(a_checking, QDate::currentDate().addDays(4))==MyMoneyMoney(0,1));
+ CPPUNIT_ASSERT(a.forecastBalance(a_checking, 4)==MyMoneyMoney(0,1));
+ CPPUNIT_ASSERT(a.forecastBalance(a_checking, QDate::currentDate().addDays(10))==MyMoneyMoney(0,1));
+ CPPUNIT_ASSERT(a.forecastBalance(a_checking, 10)==MyMoneyMoney(0,1));
+
+ //test it returns valid results
+ MyMoneyMoney b_credit = file->balance(a_credit.id(), QDate::currentDate());
+ CPPUNIT_ASSERT(a.forecastBalance(a_credit, QDate::currentDate())==file->balance(a_credit.id(), QDate::currentDate()));
+ CPPUNIT_ASSERT(a.forecastBalance(a_credit, QDate::currentDate().addDays(1))==b_credit+(moT2-moT1));
+ CPPUNIT_ASSERT(a.forecastBalance(a_credit, QDate::currentDate().addDays(2))==b_credit+((moT2-moT1)*2));
+ CPPUNIT_ASSERT(a.forecastBalance(a_credit, QDate::currentDate().addDays(3))==b_credit+((moT2-moT1)*3));
+}
+
+void MyMoneyForecastTest::testIsForecastAccount()
+{
+ MyMoneyForecast a;
+
+ MyMoneyAccount a_checking = file->account(acChecking);
+ MyMoneyAccount a_solo = file->account(acSolo);
+ MyMoneyAccount a_investment = file->account(acInvestment);
+
+ //test an invalid account
+ CPPUNIT_ASSERT(a.isForecastAccount(a_solo)==false);
+ CPPUNIT_ASSERT(a.isForecastAccount(a_investment)==true);
+
+ //test a valid account
+ CPPUNIT_ASSERT(a.isForecastAccount(a_checking)==true);
+
+}
+
+void MyMoneyForecastTest::testDoFutureScheduledForecast()
+{
+ //set up future transactions
+ MyMoneyForecast a;
+
+ MyMoneyAccount a_cash = file->account(acCash);
+ TransactionHelper t1( QDate::currentDate().addDays(1), MyMoneySplit::ActionDeposit, -moT1, acCash, acParent );
+ TransactionHelper t2( QDate::currentDate().addDays(2), MyMoneySplit::ActionDeposit, -moT2, acCash, acParent );
+ TransactionHelper t3( QDate::currentDate().addDays(3), MyMoneySplit::ActionDeposit, -moT3, acCash, acParent );
+ TransactionHelper t4( QDate::currentDate().addDays(10), MyMoneySplit::ActionDeposit, -moT4, acCash, acParent );
+
+ a.setForecastMethod(0);
+ a.setForecastDays(3);
+ a.setAccountsCycle(1);
+ a.setForecastCycles(1);
+ a.doForecast();
+
+ MyMoneyMoney b_cash = file->balance(a_cash.id(), QDate::currentDate());
+
+ //test valid results
+ CPPUNIT_ASSERT(a.forecastBalance(a_cash, QDate::currentDate())==b_cash);
+ CPPUNIT_ASSERT(a.forecastBalance(a_cash, QDate::currentDate().addDays(1))==b_cash+moT1);
+ CPPUNIT_ASSERT(a.forecastBalance(a_cash, QDate::currentDate().addDays(2))==b_cash+moT1+moT2);
+ CPPUNIT_ASSERT(a.forecastBalance(a_cash, QDate::currentDate().addDays(3))==b_cash+moT1+moT2+moT3);
+}
+
+void MyMoneyForecastTest::testScheduleForecast()
+{
+ //set up schedule environment for testing
+ MyMoneyAccount a_cash = file->account(acCash);
+ MyMoneyAccount a_parent = file->account(acParent);
+
+ MyMoneyFileTransaction ft;
+ MyMoneySchedule sch( "A Name",
+ MyMoneySchedule::TYPE_BILL,
+ MyMoneySchedule::OCCUR_WEEKLY, 1,
+ MyMoneySchedule::STYPE_DIRECTDEBIT,
+ QDate::currentDate().addDays(1),
+ QDate(),
+ true,
+ true);
+
+ MyMoneyTransaction t;
+ t.setPostDate(QDate::currentDate().addDays(1));
+ t.setEntryDate(QDate::currentDate().addDays(1));
+ //t.setId("T000000000000000001");
+ t.setBankID("BID");
+ t.setMemo("Wohnung:Miete");
+ t.setCommodity("USD");
+ t.setValue("key", "value");
+
+ MyMoneySplit s;
+ s.setPayeeId("P000001");
+ s.setShares(moT2);
+ s.setValue(moT2);
+ s.setAccountId(a_parent.id());
+ s.setBankID("SPID1");
+ s.setReconcileFlag(MyMoneySplit::Reconciled);
+ t.addSplit(s);
+
+ s.setPayeeId("P000001");
+ s.setShares(-moT2);
+ s.setValue(-moT2);
+ s.setAccountId(a_cash.id());
+ s.setBankID("SPID2");
+ s.setReconcileFlag(MyMoneySplit::Cleared);
+ s.clearId();
+ t.addSplit(s);
+
+ sch.setTransaction(t);
+
+ file->addSchedule(sch);
+ ft.commit();
+
+ MyMoneyFileTransaction ft3;
+ MyMoneySchedule sch3( "A Name1",
+ MyMoneySchedule::TYPE_BILL,
+ MyMoneySchedule::OCCUR_WEEKLY, 1,
+ MyMoneySchedule::STYPE_DIRECTDEBIT,
+ QDate::currentDate().addDays(5),
+ QDate(),
+ true,
+ true);
+
+ //sch.setLastPayment(QDate::currentDate());
+ //sch.recordPayment(QDate::currentDate().addDays(1));
+ //sch.setId("SCH0001");
+
+ MyMoneyTransaction t3;
+ t3.setPostDate(QDate::currentDate().addDays(5));
+ t3.setEntryDate(QDate::currentDate().addDays(5));
+ //t.setId("T000000000000000001");
+ t3.setBankID("BID");
+ t3.setMemo("Wohnung:Miete");
+ t3.setCommodity("USD");
+ t3.setValue("key", "value");
+
+ MyMoneySplit s3;
+ s3.setPayeeId("P000001");
+ s3.setShares(moT2);
+ s3.setValue(moT2);
+ s3.setAccountId(a_parent.id());
+ s3.setBankID("SPID1");
+ s3.setReconcileFlag(MyMoneySplit::Reconciled);
+ t3.addSplit(s3);
+
+ s3.setPayeeId("P000001");
+ s3.setShares(-moT2);
+ s3.setValue(-moT2);
+ s3.setAccountId(a_cash.id());
+ s3.setBankID("SPID2");
+ s3.setReconcileFlag(MyMoneySplit::Cleared);
+ s3.clearId();
+ t3.addSplit(s3);
+
+ sch3.setTransaction(t3);
+
+ file->addSchedule(sch3);
+ ft3.commit();
+
+
+ MyMoneyFileTransaction ft2;
+ MyMoneySchedule sch2( "A Name2",
+ MyMoneySchedule::TYPE_BILL,
+ MyMoneySchedule::OCCUR_WEEKLY, 1,
+ MyMoneySchedule::STYPE_DIRECTDEBIT,
+ QDate::currentDate().addDays(2),
+ QDate(),
+ true,
+ true);
+
+ //sch.setLastPayment(QDate::currentDate());
+ //sch.recordPayment(QDate::currentDate().addDays(1));
+ //sch.setId("SCH0001");
+
+ MyMoneyTransaction t2;
+ t2.setPostDate(QDate::currentDate().addDays(2));
+ t2.setEntryDate(QDate::currentDate().addDays(2));
+ //t.setId("T000000000000000001");
+ t2.setBankID("BID");
+ t2.setMemo("Wohnung:Miete");
+ t2.setCommodity("USD");
+ t2.setValue("key", "value");
+
+ MyMoneySplit s2;
+ s2.setPayeeId("P000001");
+ s2.setShares(moT1);
+ s2.setValue(moT1);
+ s2.setAccountId(a_parent.id());
+ s2.setBankID("SPID1");
+ s2.setReconcileFlag(MyMoneySplit::Reconciled);
+ t2.addSplit(s2);
+
+ s2.setPayeeId("P000001");
+ s2.setShares(-moT1);
+ s2.setValue(-moT1);
+ s2.setAccountId(a_cash.id());
+ s2.setBankID("SPID2");
+ s2.setReconcileFlag(MyMoneySplit::Cleared);
+ s2.clearId();
+ t2.addSplit(s2);
+
+ sch2.setTransaction(t2);
+
+ file->addSchedule(sch2);
+
+ ft2.commit();
+
+ //run forecast
+ MyMoneyForecast a;
+ a.setForecastMethod(0);
+ a.setForecastDays(3);
+ a.setAccountsCycle(1);
+ a.setForecastCycles(1);
+ a.doForecast();
+
+ //check result for single schedule
+ MyMoneyMoney b_cash = file->balance(a_cash.id(), QDate::currentDate());
+ MyMoneyMoney b_cash1 = a.forecastBalance(a_cash, QDate::currentDate().addDays(1));
+
+ //test valid results
+ CPPUNIT_ASSERT(a.forecastBalance(a_cash, QDate::currentDate())==b_cash);
+ CPPUNIT_ASSERT(a.forecastBalance(a_cash, QDate::currentDate().addDays(1))==b_cash-moT2);
+ CPPUNIT_ASSERT(a.forecastBalance(a_cash, QDate::currentDate().addDays(2))==b_cash-moT2-moT1);
+}
+
+
+void MyMoneyForecastTest::testDaysToMinimumBalance()
+{
+ //setup environment
+ MyMoneyForecast a;
+
+ MyMoneyAccount a_cash = file->account(acCash);
+ MyMoneyAccount a_credit = file->account(acCredit);
+ MyMoneyAccount a_parent = file->account(acParent);
+ a_cash.setValue("minBalanceAbsolute", "50");
+ a_credit.setValue("minBalanceAbsolute", "50");
+ TransactionHelper t1( QDate::currentDate().addDays(-1), MyMoneySplit::ActionDeposit, -moT1, acCash, acParent );
+ TransactionHelper t2( QDate::currentDate().addDays(2), MyMoneySplit::ActionDeposit, moT2, acCash, acParent );
+ TransactionHelper t3( QDate::currentDate().addDays(-1), MyMoneySplit::ActionWithdrawal, -moT1, acCredit, acParent );
+ TransactionHelper t4( QDate::currentDate().addDays(4), MyMoneySplit::ActionWithdrawal, moT5, acCredit, acParent );
+
+ a.setForecastMethod(0);
+ a.setForecastDays(3);
+ a.setAccountsCycle(1);
+ a.setForecastCycles(1);
+ a.setBeginForecastDay(0);
+ a.doForecast();
+
+ //test invalid arguments
+ MyMoneyAccount nullAcc;
+ CPPUNIT_ASSERT(a.daysToMinimumBalance(nullAcc) == -1);
+
+ //test when not a forecast account
+ CPPUNIT_ASSERT(a.daysToMinimumBalance(a_parent) == -1);
+
+ //test it warns when inside the forecast period
+ CPPUNIT_ASSERT(a.daysToMinimumBalance(a_cash) == 2);
+
+ //test it does not warn when it will be outside of the forecast period
+ CPPUNIT_ASSERT(a.daysToMinimumBalance(a_credit) == -1);
+}
+void MyMoneyForecastTest::testDaysToZeroBalance()
+{
+ //set up environment
+ MyMoneyAccount a_Solo = file->account(acSolo);
+ MyMoneyAccount a_Cash = file->account(acCash);
+ MyMoneyAccount a_Credit = file->account(acCredit);
+
+ //MyMoneyFileTransaction ft;
+ TransactionHelper t1( QDate::currentDate().addDays(2), MyMoneySplit::ActionWithdrawal, -moT1, acChecking, acSolo );
+ TransactionHelper t2( QDate::currentDate().addDays(2), MyMoneySplit::ActionTransfer, (moT5), acCash, acCredit );
+ TransactionHelper t3( QDate::currentDate().addDays(2), MyMoneySplit::ActionWithdrawal, (moT5*100), acCredit, acParent );
+ //ft.commit();
+
+ MyMoneyForecast a;
+ a.setForecastMethod(0);
+ a.setForecastDays(30);
+ a.setAccountsCycle(1);
+ a.setForecastCycles(3);
+ a.doForecast();
+
+ //test invalid arguments
+ MyMoneyAccount nullAcc;
+ try {
+ a.daysToZeroBalance(nullAcc);
+ }
+ catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ //test when not a forecast account
+ MyMoneyAccount a_solo = file->account(acSolo);
+ int iSolo = a.daysToZeroBalance(a_Solo);
+
+ CPPUNIT_ASSERT(iSolo == -2);
+
+ //test it warns when inside the forecast period
+
+ MyMoneyMoney fCash = a.forecastBalance(a_Cash, QDate::currentDate().addDays(2));
+
+ CPPUNIT_ASSERT(a.daysToZeroBalance(a_Cash) == 2);
+
+ //test it does not warn when it will be outside of the forecast period
+
+}
+
+void MyMoneyForecastTest::testSkipOpeningDate()
+{
+ //set up environment
+ MyMoneyForecast a;
+
+ TransactionHelper t1( QDate::currentDate().addDays(-2), MyMoneySplit::ActionWithdrawal, this->moT1, acCash, acSolo);
+ TransactionHelper t2( QDate::currentDate().addDays(-1), MyMoneySplit::ActionWithdrawal, this->moT2, acCash, acSolo);
+
+ a.setForecastMethod(1);
+ a.setForecastDays(3);
+ a.setAccountsCycle(2);
+ a.setForecastCycles(1);
+ a.setHistoryMethod(0);
+ a.doForecast();
+
+ MyMoneyAccount a_cash = file->account(acCash);
+
+ //test it has no variation because it skipped the variation of the opening date
+ MyMoneyMoney b_cash = file->balance(a_cash.id(), QDate::currentDate());
+ CPPUNIT_ASSERT(a.skipOpeningDate() == true);
+ CPPUNIT_ASSERT(a.forecastBalance(a_cash, QDate::currentDate())==b_cash);
+ CPPUNIT_ASSERT(a.forecastBalance(a_cash, QDate::currentDate().addDays(1))==b_cash);
+ CPPUNIT_ASSERT(a.forecastBalance(a_cash, QDate::currentDate().addDays(2))==b_cash-moT2);
+ CPPUNIT_ASSERT(a.forecastBalance(a_cash, QDate::currentDate().addDays(3))==b_cash-moT2);
+}
+
+void MyMoneyForecastTest::testAccountMinimumBalanceDateList() {
+
+ //set up environment
+ MyMoneyForecast a;
+
+ TransactionHelper t1( QDate::currentDate().addDays(-2), MyMoneySplit::ActionWithdrawal, this->moT1, acCash, acSolo);
+ TransactionHelper t2( QDate::currentDate().addDays(-1), MyMoneySplit::ActionWithdrawal, this->moT2, acCash, acSolo);
+
+ a.setForecastMethod(1);
+ a.setForecastDays(6);
+ a.setAccountsCycle(2);
+ a.setForecastCycles(3);
+ a.setHistoryMethod(0);
+ a.setBeginForecastDay(QDate::currentDate().addDays(1).day());
+ a.doForecast();
+
+ MyMoneyAccount a_cash = file->account(acCash);
+
+ //test
+ QValueList<QDate> dateList;
+ dateList = a.accountMinimumBalanceDateList(a_cash);
+
+ QValueList<QDate>::iterator it = dateList.begin();
+
+ QDate minDate = *it;
+
+ CPPUNIT_ASSERT(minDate==QDate::currentDate().addDays(2));
+ it++;
+ minDate = *it;
+ CPPUNIT_ASSERT(minDate==QDate::currentDate().addDays(4));
+ it++;
+ minDate = *it;
+ CPPUNIT_ASSERT(minDate==QDate::currentDate().addDays(6));
+
+}
+
+void MyMoneyForecastTest::testAccountMaximumBalanceDateList() {
+ //set up environment
+ MyMoneyForecast a;
+
+ TransactionHelper t1( QDate::currentDate().addDays(-2), MyMoneySplit::ActionWithdrawal, this->moT1, acCash, acSolo);
+ TransactionHelper t2( QDate::currentDate().addDays(-1), MyMoneySplit::ActionWithdrawal, this->moT2, acCash, acSolo);
+
+ a.setForecastMethod(1);
+ a.setForecastDays(6);
+ a.setAccountsCycle(2);
+ a.setForecastCycles(3);
+ a.setHistoryMethod(0);
+ a.setBeginForecastDay(QDate::currentDate().addDays(1).day());
+ a.doForecast();
+
+ MyMoneyAccount a_cash = file->account(acCash);
+
+ //test
+ QValueList<QDate> dateList;
+ dateList = a.accountMaximumBalanceDateList(a_cash);
+
+ QValueList<QDate>::iterator it = dateList.begin();
+
+ QDate maxDate = *it;
+
+ CPPUNIT_ASSERT(maxDate==QDate::currentDate().addDays(1));
+ it++;
+ maxDate = *it;
+ CPPUNIT_ASSERT(maxDate==QDate::currentDate().addDays(3));
+ it++;
+ maxDate = *it;
+ CPPUNIT_ASSERT(maxDate==QDate::currentDate().addDays(5));
+
+
+}
+
+void MyMoneyForecastTest::testAccountAverageBalance() {
+ //set up environment
+ MyMoneyForecast a;
+
+ TransactionHelper t1( QDate::currentDate().addDays(-2), MyMoneySplit::ActionWithdrawal, this->moT1, acCash, acSolo);
+ TransactionHelper t2( QDate::currentDate().addDays(-1), MyMoneySplit::ActionWithdrawal, this->moT2, acCash, acSolo);
+
+ a.setForecastMethod(1);
+ a.setForecastDays(3);
+ a.setAccountsCycle(2);
+ a.setForecastCycles(1);
+ a.setBeginForecastDay(0);
+ a.doForecast();
+
+ MyMoneyAccount a_cash = file->account(acCash);
+
+ //test
+ MyMoneyMoney b_cash1 = a.forecastBalance(a_cash, QDate::currentDate().addDays(1));
+ MyMoneyMoney b_cash2 = a.forecastBalance(a_cash, QDate::currentDate().addDays(2));
+ MyMoneyMoney b_cash3 = a.forecastBalance(a_cash, QDate::currentDate().addDays(3));
+
+ MyMoneyMoney average = (b_cash1 + b_cash2 +b_cash3)/MyMoneyMoney(3,1);
+
+
+ CPPUNIT_ASSERT(a.accountAverageBalance(a_cash)==average);
+}
+
+void MyMoneyForecastTest::testBeginForecastDate() {
+ //set up environment
+ MyMoneyForecast a;
+ QDate beginDate;
+ int beginDay;
+
+ a.setForecastMethod(1);
+ a.setForecastDays(90);
+ a.setAccountsCycle(14);
+ a.setForecastCycles(3);
+ a.setBeginForecastDay(0);
+ a.doForecast();
+
+ //test when using old method without begin day
+ CPPUNIT_ASSERT(QDate::currentDate() == a.beginForecastDate());
+
+ //setup begin to last day of month
+ a.setBeginForecastDay(31);
+ beginDay = a.beginForecastDay();
+ a.doForecast();
+
+ //test
+ if(QDate::currentDate().day() < beginDay)
+ {
+ if(QDate::currentDate().daysInMonth() < beginDay)
+ beginDay = QDate::currentDate().daysInMonth();
+
+ beginDate = QDate(QDate::currentDate().year(), QDate::currentDate().month(), beginDay);
+
+ CPPUNIT_ASSERT(beginDate == a.beginForecastDate());
+ }
+
+ //setup begin day to same date
+ a.setBeginForecastDay(QDate::currentDate().day());
+ beginDay = a.beginForecastDay();
+ a.doForecast();
+
+ CPPUNIT_ASSERT(QDate::currentDate() == a.beginForecastDate());
+
+ //setup to first day of month with small interval
+ a.setBeginForecastDay(1);
+ a.setAccountsCycle(1);
+ beginDay = a.beginForecastDay();
+ a.doForecast();
+
+ //test
+ if(QDate::currentDate() == a.beginForecastDate()) {
+ CPPUNIT_ASSERT(QDate::currentDate() == a.beginForecastDate());
+ } else {
+ beginDay = ((((QDate::currentDate().day() - beginDay)/a.accountsCycle()) + 1) * a.accountsCycle()) + beginDay;
+ if(beginDay > QDate::currentDate().daysInMonth())
+ beginDay = QDate::currentDate().daysInMonth();
+ beginDate = QDate(QDate::currentDate().year(), QDate::currentDate().month(), beginDay);
+ if(QDate::currentDate().day() == QDate::currentDate().daysInMonth() ) {
+ std::cout << std::endl << "testBeginForecastDate(): test of first day of month with small interval skipped because it is the last day of month" << std::endl;
+ } else {
+ CPPUNIT_ASSERT(beginDate == a.beginForecastDate());
+ }
+ }
+
+ //setup to test when current date plus cycle equals begin day
+ a.setAccountsCycle(14);
+ beginDay = QDate::currentDate().addDays(14).day();
+ a.setBeginForecastDay(beginDay);
+ beginDate = QDate::currentDate().addDays(14);
+ a.doForecast();
+
+ //test
+ CPPUNIT_ASSERT(beginDate == a.beginForecastDate());
+
+ //setup to test when the begin day will be next month
+ a.setBeginForecastDay(1);
+ a.setAccountsCycle(40);
+ a.doForecast();
+
+ beginDate = QDate(QDate::currentDate().addMonths(1).year(), QDate::currentDate().addMonths(1).month(), 1);
+
+ //test
+ if(QDate::currentDate().day() > 1) {
+ CPPUNIT_ASSERT(beginDate == a.beginForecastDate());
+ } else {
+ //test is not valid if today is 1st of month
+ std::cout << std::endl << "testBeginForecastDate(): test of first day of month skipped because current day is 1st of month" << std::endl;
+ }
+}
+
+ void MyMoneyForecastTest::testHistoryDays(void)
+{
+ MyMoneyForecast a;
+
+ CPPUNIT_ASSERT(a.historyStartDate() == QDate::currentDate().addDays(-a.forecastCycles()*a.accountsCycle()) );
+ CPPUNIT_ASSERT(a.historyEndDate() == QDate::currentDate().addDays(-1) );
+ CPPUNIT_ASSERT(a.historyDays() == a.forecastCycles()*a.accountsCycle());
+
+ a.setForecastMethod(1);
+ a.setForecastDays(90);
+ a.setAccountsCycle(14);
+ a.setForecastCycles(3);
+ a.setBeginForecastDay(0);
+ a.doForecast();
+
+ CPPUNIT_ASSERT(a.historyStartDate() == QDate::currentDate().addDays(-14*3) );
+ CPPUNIT_ASSERT(a.historyDays() == (14*3));
+ CPPUNIT_ASSERT(a.historyEndDate() == (QDate::currentDate().addDays(-1)) );
+}
+
+void MyMoneyForecastTest::testCreateBudget()
+{
+ //set up environment
+ MyMoneyForecast a;
+ MyMoneyForecast b;
+ MyMoneyBudget budget;
+
+ TransactionHelper t1( QDate(2005, 1, 3), MyMoneySplit::ActionWithdrawal, this->moT1, acCash, acSolo);
+ TransactionHelper t2( QDate(2005, 1, 15), MyMoneySplit::ActionWithdrawal, this->moT2, acCash, acParent);
+ TransactionHelper t3( QDate(2005, 1, 30), MyMoneySplit::ActionWithdrawal, this->moT3, acCash, acSolo);
+ TransactionHelper t4( QDate(2006, 1, 25), MyMoneySplit::ActionWithdrawal, this->moT4, acCash, acParent);
+ TransactionHelper t5( QDate(2005, 4, 3), MyMoneySplit::ActionWithdrawal, this->moT1, acCash, acSolo);
+ TransactionHelper t6( QDate(2006, 5, 15), MyMoneySplit::ActionWithdrawal, this->moT2, acCash, acParent);
+ TransactionHelper t7( QDate(2005, 8, 3), MyMoneySplit::ActionWithdrawal, this->moT3, acCash, acSolo);
+ TransactionHelper t8( QDate(2006, 9, 15), MyMoneySplit::ActionWithdrawal, this->moT4, acCash, acParent);
+
+ a.setHistoryMethod(0);
+ a.setForecastMethod(1);
+ a.createBudget(budget, QDate(2005, 1, 1), QDate(2006, 12, 31), QDate(2007, 1, 1), QDate(2007, 12, 31), true);
+
+ //test
+ MyMoneyAccount a_solo = file->account(acSolo);
+ MyMoneyAccount a_parent = file->account(acParent);
+
+ //test it has no variation because it skipped the variation of the opening date
+ CPPUNIT_ASSERT(a.forecastBalance(a_solo, QDate(2007, 1, 1)) == ((moT1+moT3)/MyMoneyMoney(2, 1)));
+ CPPUNIT_ASSERT(a.forecastBalance(a_parent, QDate(2007, 1, 1)) == ((moT2+moT4)/MyMoneyMoney(2, 1)));
+ CPPUNIT_ASSERT(a.forecastBalance(a_solo, QDate(2007, 4, 1)) == ((moT1)/MyMoneyMoney(2, 1)));
+ CPPUNIT_ASSERT(a.forecastBalance(a_parent, QDate(2007, 5, 1)) == ((moT2)/MyMoneyMoney(2, 1)));
+ CPPUNIT_ASSERT(a.forecastBalance(a_solo, QDate(2007, 8, 1)) == ((moT3)/MyMoneyMoney(2, 1)));
+ CPPUNIT_ASSERT(a.forecastBalance(a_parent, QDate(2007, 9, 1)) == ((moT4)/MyMoneyMoney(2, 1)));
+ //test the budget object returned by the method
+ CPPUNIT_ASSERT(budget.account(a_parent.id()).period(QDate(2007, 9, 1)).amount() == ((moT4)/MyMoneyMoney(2, 1)));
+
+ //setup test for a length lower than a year
+ b.setForecastMethod(1);
+ b.setHistoryMethod(0);
+ b.createBudget(budget, QDate(2005, 1, 1), QDate(2005, 6, 30), QDate(2007, 1, 1), QDate(2007, 6, 30), true);
+
+ //test
+ CPPUNIT_ASSERT(b.forecastBalance(a_solo, QDate(2007, 1, 1)) == (moT1+moT3));
+ CPPUNIT_ASSERT(b.forecastBalance(a_parent, QDate(2007, 1, 1)) == (moT2));
+ CPPUNIT_ASSERT(b.forecastBalance(a_solo, QDate(2007, 4, 1)) == (moT1));
+ CPPUNIT_ASSERT(b.forecastBalance(a_parent, QDate(2007, 5, 1)) == (MyMoneyMoney(0, 1)));
+
+ //set up schedule environment for testing
+ MyMoneyAccount a_cash = file->account(acCash);
+
+ MyMoneyFileTransaction ft;
+ MyMoneySchedule sch( "A Name",
+ MyMoneySchedule::TYPE_BILL,
+ MyMoneySchedule::OCCUR_MONTHLY, 1,
+ MyMoneySchedule::STYPE_DIRECTDEBIT,
+ QDate::currentDate(),
+ QDate(),
+ true,
+ true);
+
+ MyMoneyTransaction t10;
+ t10.setPostDate(QDate::currentDate().addMonths(1));
+ t10.setEntryDate(QDate::currentDate().addMonths(1));
+ //t.setId("T000000000000000001");
+ t10.setBankID("BID");
+ t10.setMemo("Wohnung:Miete");
+ t10.setCommodity("USD");
+ t10.setValue("key", "value");
+
+ MyMoneySplit s;
+ s.setPayeeId("P000001");
+ s.setShares(moT2);
+ s.setValue(moT2);
+ s.setAccountId(a_parent.id());
+ s.setBankID("SPID1");
+ s.setReconcileFlag(MyMoneySplit::Reconciled);
+ t10.addSplit(s);
+
+ s.setPayeeId("P000001");
+ s.setShares(-moT2);
+ s.setValue(-moT2);
+ s.setAccountId(a_cash.id());
+ s.setBankID("SPID2");
+ s.setReconcileFlag(MyMoneySplit::Cleared);
+ s.clearId();
+ t10.addSplit(s);
+
+ sch.setTransaction(t10);
+
+ file->addSchedule(sch);
+ ft.commit();
+
+ //run forecast
+ MyMoneyForecast c;
+ c.setForecastMethod(0);
+ c.setForecastCycles(1);
+ c.createBudget(budget, QDate::currentDate().addYears(-2), QDate::currentDate().addYears(-1), QDate::currentDate().addMonths(-2), QDate::currentDate().addMonths(6), true);
+
+ MyMoneyMoney c_parent = c.forecastBalance(a_parent, QDate(QDate::currentDate().addMonths(1).year(), QDate::currentDate().addMonths(1).month(), 1) );
+
+ //test valid results
+ CPPUNIT_ASSERT(c.forecastBalance(a_parent, QDate(QDate::currentDate().addMonths(1).year(), QDate::currentDate().addMonths(1).month(), 1) ) == (moT2));
+}
+
+void MyMoneyForecastTest::testLinearRegression() {
+ //set up environment
+ MyMoneyForecast a;
+
+ MyMoneyAccount a_checking = file->account(acChecking);
+ MyMoneyAccount a_credit = file->account(acCredit);
+
+ //setup some transactions
+ TransactionHelper t1( QDate::currentDate().addDays(-1), MyMoneySplit::ActionWithdrawal, this->moT1, acChecking, acSolo);
+ TransactionHelper t2( QDate::currentDate().addDays(-1), MyMoneySplit::ActionDeposit, -(this->moT2), acCredit, acParent);
+ TransactionHelper t3( QDate::currentDate().addDays(-1), MyMoneySplit::ActionTransfer, this->moT1, acCredit, acChecking);
+
+//TODO Add tests specific for linear regression
+
+
+}
diff --git a/kmymoney2/mymoney/mymoneyforecasttest.h b/kmymoney2/mymoney/mymoneyforecasttest.h
new file mode 100644
index 0000000..9ec2ae9
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyforecasttest.h
@@ -0,0 +1,96 @@
+/***************************************************************************
+ mymoneyforecasttest.h
+ -------------------
+ copyright : (C) 2007 by Alvaro Soliverez
+ email : asoliverez@gmail.com
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 __MYMONEYFORECASTTEST_H__
+#define __MYMONEYFORECASTTEST_H__
+
+#include <cppunit/extensions/HelperMacros.h>
+
+#include "../mymoney/mymoneyfile.h"
+#include "../mymoney/storage/mymoneyseqaccessmgr.h"
+
+#define private public
+#include "mymoneyforecast.h"
+#undef private
+
+
+class MyMoneyForecastTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE ( MyMoneyForecastTest );
+ CPPUNIT_TEST ( testEmptyConstructor );
+ CPPUNIT_TEST ( testDoForecast );
+ CPPUNIT_TEST ( testDoForecastInit );
+ CPPUNIT_TEST ( testGetForecastAccountList );
+ CPPUNIT_TEST ( testCalculateAccountTrend );
+ CPPUNIT_TEST ( testGetForecastBalance );
+ CPPUNIT_TEST ( testIsForecastAccount );
+ CPPUNIT_TEST ( testDoFutureScheduledForecast );
+ CPPUNIT_TEST ( testDaysToMinimumBalance );
+ CPPUNIT_TEST ( testDaysToZeroBalance );
+ CPPUNIT_TEST ( testScheduleForecast );
+ CPPUNIT_TEST ( testSkipOpeningDate );
+ CPPUNIT_TEST ( testAccountMinimumBalanceDateList );
+ CPPUNIT_TEST ( testAccountMaximumBalanceDateList );
+ CPPUNIT_TEST ( testAccountAverageBalance );
+ CPPUNIT_TEST ( testBeginForecastDate );
+ CPPUNIT_TEST ( testHistoryDays );
+ CPPUNIT_TEST ( testCreateBudget );
+ CPPUNIT_TEST ( testLinearRegression );
+ CPPUNIT_TEST_SUITE_END();
+
+
+
+ public:
+ MyMoneyForecastTest();
+ void setUp ();
+ void tearDown ();
+ void testEmptyConstructor();
+ void testDoForecast();
+ void testDoForecastInit();
+ void testGetForecastAccountList();
+ void testCalculateAccountTrend();
+ void testGetForecastBalance();
+ void testIsForecastAccount();
+ void testDoFutureScheduledForecast();
+ void testDaysToMinimumBalance();
+ void testDaysToZeroBalance();
+ void testScheduleForecast();
+ void testSkipOpeningDate();
+ void testAccountMinimumBalanceDateList();
+ void testAccountMaximumBalanceDateList();
+ void testAccountAverageBalance();
+ void testBeginForecastDate();
+ void testHistoryDays();
+ void testCreateBudget();
+ void testLinearRegression();
+
+ protected:
+ MyMoneyForecast *m;
+
+ private:
+ MyMoneyAccount *account;
+
+ MyMoneySeqAccessMgr* storage;
+ MyMoneyFile* file;
+
+ MyMoneyMoney moT1;
+ MyMoneyMoney moT2;
+ MyMoneyMoney moT3;
+ MyMoneyMoney moT4;
+ MyMoneyMoney moT5;
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneyinstitution.cpp b/kmymoney2/mymoney/mymoneyinstitution.cpp
new file mode 100644
index 0000000..9969ee7
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyinstitution.cpp
@@ -0,0 +1,182 @@
+/***************************************************************************
+ mymoneyinstitution.cpp
+ -------------------
+ copyright : (C) 2001 by Michael Edwardes,
+ 2002-2005 by Thomas Baumgart
+ email : mte@users.sourceforge.net,
+ ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+// ----------------------------------------------------------------------------
+// KDE Includes
+#include <kglobal.h>
+#include <kstandarddirs.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "mymoneyinstitution.h"
+#include <kmymoney/mymoneyexception.h>
+
+MyMoneyInstitution::MyMoneyInstitution()
+{
+}
+
+MyMoneyInstitution::MyMoneyInstitution(const QString& id, const MyMoneyInstitution& right) :
+ MyMoneyObject(id)
+{
+ *this = right;
+ m_id = id;
+}
+
+MyMoneyInstitution::MyMoneyInstitution(const QString& name,
+ const QString& town,
+ const QString& street,
+ const QString& postcode,
+ const QString& telephone,
+ const QString& manager,
+ const QString& sortcode)
+{
+ clearId();
+ m_name = name;
+ m_town = town;
+ m_street = street;
+ m_postcode = postcode;
+ m_telephone = telephone;
+ m_manager = manager;
+ m_sortcode = sortcode;
+}
+
+MyMoneyInstitution::MyMoneyInstitution(const QDomElement& node) :
+ MyMoneyObject(node),
+ MyMoneyKeyValueContainer(node.elementsByTagName("KEYVALUEPAIRS").item(0).toElement())
+{
+ if("INSTITUTION" != node.tagName())
+ throw new 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 new 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(unsigned int i = 0; i < nodeList.count(); ++i) {
+ m_accountList << nodeList.item(i).toElement().attribute("id");
+ }
+ }
+}
+
+MyMoneyInstitution::~MyMoneyInstitution()
+{
+}
+
+void MyMoneyInstitution::addAccountId(const QString& account)
+{
+ // only add this account if it is not yet presently in the list
+ if(m_accountList.contains(account) == 0)
+ m_accountList.append(account);
+}
+
+QString MyMoneyInstitution::removeAccountId(const QString& account)
+{
+ QStringList::Iterator pos;
+ QString rc;
+
+ pos = m_accountList.find(account);
+ if(pos != m_accountList.end()) {
+ m_accountList.remove(pos);
+ rc = account;
+ }
+ return rc;
+}
+
+bool MyMoneyInstitution::operator < (const MyMoneyInstitution& right) const
+{
+ return m_name < right.m_name;
+}
+
+bool MyMoneyInstitution::operator == (const MyMoneyInstitution& right) const
+{
+ if (MyMoneyObject::operator==(right) &&
+ ((m_name.length() == 0 && right.m_name.length() == 0) || (m_name == right.m_name)) &&
+ ((m_town.length() == 0 && right.m_town.length() == 0) || (m_town == right.m_town)) &&
+ ((m_street.length() == 0 && right.m_street.length() == 0) || (m_street == right.m_street)) &&
+ ((m_postcode.length() == 0 && right.m_postcode.length() == 0) || (m_postcode == right.m_postcode)) &&
+ ((m_telephone.length() == 0 && right.m_telephone.length() == 0) || (m_telephone == right.m_telephone)) &&
+ ((m_sortcode.length() == 0 && right.m_sortcode.length() == 0) || (m_sortcode == right.m_sortcode)) &&
+ ((m_manager.length() == 0 && right.m_manager.length() == 0) || (m_manager == right.m_manager)) &&
+ (m_accountList == right.m_accountList) ) {
+ return true;
+ } else
+ return false;
+}
+
+void MyMoneyInstitution::writeXML(QDomDocument& document, QDomElement& parent) const
+{
+ QDomElement el = document.createElement("INSTITUTION");
+
+ writeBaseXML(document, el);
+
+ el.setAttribute("name", m_name);
+ el.setAttribute("manager", m_manager);
+ el.setAttribute("sortcode", m_sortcode);
+
+ QDomElement address = document.createElement("ADDRESS");
+ address.setAttribute("street", m_street);
+ address.setAttribute("city", m_town);
+ address.setAttribute("zip", m_postcode);
+ address.setAttribute("telephone", m_telephone);
+ el.appendChild(address);
+
+
+ QDomElement accounts = document.createElement("ACCOUNTIDS");
+ for(QStringList::ConstIterator it = accountList().begin(); it != accountList().end(); ++it){
+ QDomElement temp = document.createElement("ACCOUNTID");
+ temp.setAttribute("id", (*it));
+ accounts.appendChild(temp);
+ }
+ el.appendChild(accounts);
+
+ //Add in Key-Value Pairs for institutions.
+ MyMoneyKeyValueContainer::writeXML(document, el);
+
+ parent.appendChild(el);
+}
+
+bool MyMoneyInstitution::hasReferenceTo(const QString& /* id */) const
+{
+ bool rc = false;
+ return rc;
+}
+
+QPixmap MyMoneyInstitution::pixmap() const {
+ return QPixmap(KGlobal::dirs()->findResource("appdata",QString( "icons/hicolor/22x22/actions/%1.png").arg("bank")));
+}
+
diff --git a/kmymoney2/mymoney/mymoneyinstitution.h b/kmymoney2/mymoney/mymoneyinstitution.h
new file mode 100644
index 0000000..35b44c5
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyinstitution.h
@@ -0,0 +1,206 @@
+/***************************************************************************
+ mymoneyinstitution.h
+ -------------------
+ copyright : (C) 2002-2005 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYINSTITUTION_H
+#define MYMONEYINSTITUTION_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstring.h>
+#include <qmap.h>
+#include <qstringlist.h>
+#include <qpixmap.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "mymoneyutils.h"
+#include <kmymoney/mymoneyobject.h>
+#include <kmymoney/mymoneykeyvaluecontainer.h>
+#include <kmymoney/export.h>
+
+class MyMoneyFile;
+class MyMoneyMoney;
+
+/**
+ * This class represents a Bank contained within a MyMoneyFile object
+ *
+ * @author Thomas Baumgart
+ */
+class KMYMONEY_EXPORT MyMoneyInstitution : public MyMoneyObject, public MyMoneyKeyValueContainer
+{
+public:
+ /**
+ * This is the constructor for a new empty institution description
+ */
+ MyMoneyInstitution();
+
+ /**
+ * This is the constructor used by an application to fill the
+ * values required for a new institution. This object should then be
+ * passed to @see MyMoneyFile::addInstitution
+ */
+ MyMoneyInstitution(const QString& name,
+ const QString& city,
+ const QString& street,
+ const QString& postcode,
+ const QString& telephone,
+ const QString& manager,
+ const QString& sortCode);
+
+ /**
+ * This is the destructor for any MyMoneyInstitution object
+ */
+ ~MyMoneyInstitution();
+
+ /**
+ * This is the constructor for a new institution known to the current file
+ *
+ * @param id id assigned to the new institution object
+ * @param right institution definition
+ */
+ MyMoneyInstitution(const QString& id, const MyMoneyInstitution& right);
+
+ /**
+ * This is the constructor for an institution that is described by a
+ * QDomElement (e.g. from a file).
+ *
+ * @param el const reference to the QDomElement from which to
+ * create the object
+ */
+ MyMoneyInstitution(const QDomElement& el);
+
+ const QString& manager(void) const { return m_manager; }
+ const QString& name(void) const { return m_name; }
+ const QString& postcode(void) const { return m_postcode; }
+ const QString& street(void) const { return m_street; }
+ const QString& telephone(void) const { return m_telephone; }
+ const QString& town(void) const { return m_town; }
+ const QString& city(void) const { return town(); }
+ const QString& sortcode(void) const { return m_sortcode; }
+
+ void setManager(QString manager) { m_manager = manager; }
+ void setName(QString name) { m_name = name; }
+ void setPostcode(QString code) { m_postcode = code; }
+ void setStreet(QString street) { m_street = street; }
+ void setTelephone(QString tel) { m_telephone = tel; }
+ void setTown(QString town) { m_town = town; }
+ void setCity(QString town) { setTown(town); }
+ void setSortcode(QString code) { m_sortcode = code; }
+
+ /**
+ * This method adds the id of an account to the account list of
+ * this institution It is verified, that the account is only
+ * mentioned once.
+ *
+ * @param account id of the account to be added
+ */
+ void addAccountId(const QString& account);
+
+ /**
+ * This method deletes the id of an account from the account list
+ * of this institution
+ *
+ * @param account id of the account to be deleted
+ * @return id of account deleted, otherwise empty string
+ */
+ QString removeAccountId(const QString& account);
+
+ /**
+ * This method is used to return the set of accounts known to
+ * this institution
+ * return QStringList of account ids
+ */
+ const QStringList& accountList(void) const { return m_accountList; }
+
+ /**
+ * This method returns the number of accounts known to
+ * this institution
+ * @return number of accounts
+ */
+ unsigned int accountCount(void) const { return m_accountList.count(); }
+
+ bool operator == (const MyMoneyInstitution&) const;
+ bool operator < (const MyMoneyInstitution& right) const;
+
+ void writeXML(QDomDocument& document, QDomElement& parent) const;
+
+ /**
+ * This method checks if a reference to the given object exists. It returns,
+ * a @p true if the object is referencing the one requested by the
+ * parameter @p id. If it does not, this method returns @p false.
+ *
+ * @param id id of the object to be checked for references
+ * @retval true This object references object with id @p id.
+ * @retval false This object does not reference the object with id @p id.
+ */
+ virtual bool hasReferenceTo(const QString& id) const;
+
+ QPixmap pixmap() const;
+
+private:
+ // Bank 'fields'
+ /**
+ * This member variable keeps the name of the institution
+ */
+ QString m_name;
+
+ /**
+ * This member variable keeps the city of the institution
+ */
+ QString m_town;
+
+ /**
+ * This member variable keeps the street of the institution
+ */
+ QString m_street;
+
+ /**
+ * This member variable keeps the zip-code of the institution
+ */
+ QString m_postcode;
+
+ /**
+ * This member variable keeps the telephone number of the institution
+ */
+ QString m_telephone;
+
+ /**
+ * This member variable keeps the name of the representative of
+ * the institution
+ */
+ QString m_manager;
+
+ /**
+ * This member variable keeps the sort code of the institution.
+ * FIXME: I have no idea
+ * what it is good for. I keep it because it was in the old engine.
+ */
+ QString m_sortcode;
+
+ /**
+ * This member variable keeps the sorted list of the account ids
+ * available at this institution
+ */
+ QStringList m_accountList;
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneyinstitutiontest.cpp b/kmymoney2/mymoney/mymoneyinstitutiontest.cpp
new file mode 100644
index 0000000..ce707ec
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyinstitutiontest.cpp
@@ -0,0 +1,296 @@
+/***************************************************************************
+ mymoneyinstitutiontest.cpp
+ -------------------
+ copyright : (C) 2002 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "mymoneyinstitutiontest.h"
+#include <kmymoney/mymoneyexception.h>
+
+MyMoneyInstitutionTest::MyMoneyInstitutionTest()
+{
+}
+
+
+void MyMoneyInstitutionTest::setUp () {
+ m = new MyMoneyInstitution();
+ n = new MyMoneyInstitution("name", "town", "street", "postcode",
+ "telephone", "manager", "sortcode");
+}
+
+void MyMoneyInstitutionTest::tearDown () {
+ delete m;
+ delete n;
+}
+
+void MyMoneyInstitutionTest::testEmptyConstructor() {
+ CPPUNIT_ASSERT(m->id().isEmpty());
+ CPPUNIT_ASSERT(m->street().isEmpty());
+ CPPUNIT_ASSERT(m->town().isEmpty());
+ CPPUNIT_ASSERT(m->postcode().isEmpty());
+ CPPUNIT_ASSERT(m->telephone().isEmpty());
+ CPPUNIT_ASSERT(m->manager().isEmpty());
+
+ CPPUNIT_ASSERT(m->accountCount() == 0);
+}
+
+void MyMoneyInstitutionTest::testSetFunctions() {
+ m->setStreet("street");
+ m->setTown("town");
+ m->setPostcode("postcode");
+ m->setTelephone("telephone");
+ m->setManager("manager");
+ m->setName("name");
+
+ CPPUNIT_ASSERT(m->id().isEmpty());
+ CPPUNIT_ASSERT(m->street() == "street");
+ CPPUNIT_ASSERT(m->town() == "town");
+ CPPUNIT_ASSERT(m->postcode() == "postcode");
+ CPPUNIT_ASSERT(m->telephone() == "telephone");
+ CPPUNIT_ASSERT(m->manager() == "manager");
+ CPPUNIT_ASSERT(m->name() == "name");
+}
+
+void MyMoneyInstitutionTest::testNonemptyConstructor() {
+ CPPUNIT_ASSERT(n->id().isEmpty());
+ CPPUNIT_ASSERT(n->street() == "street");
+ CPPUNIT_ASSERT(n->town() == "town");
+ CPPUNIT_ASSERT(n->postcode() == "postcode");
+ CPPUNIT_ASSERT(n->telephone() == "telephone");
+ CPPUNIT_ASSERT(n->manager() == "manager");
+ CPPUNIT_ASSERT(n->name() == "name");
+ CPPUNIT_ASSERT(n->sortcode() == "sortcode");
+}
+
+void MyMoneyInstitutionTest::testCopyConstructor() {
+ MyMoneyInstitution* n1 = new MyMoneyInstitution("GUID1", *n);
+ MyMoneyInstitution n2(*n1);
+
+ CPPUNIT_ASSERT(*n1 == n2);
+
+ delete n1;
+}
+
+void MyMoneyInstitutionTest::testMyMoneyFileConstructor() {
+ MyMoneyInstitution *t = new MyMoneyInstitution("GUID", *n);
+
+ CPPUNIT_ASSERT(t->id() == "GUID");
+
+ CPPUNIT_ASSERT(t->street() == "street");
+ CPPUNIT_ASSERT(t->town() == "town");
+ CPPUNIT_ASSERT(t->postcode() == "postcode");
+ CPPUNIT_ASSERT(t->telephone() == "telephone");
+ CPPUNIT_ASSERT(t->manager() == "manager");
+ CPPUNIT_ASSERT(t->name() == "name");
+ CPPUNIT_ASSERT(t->sortcode() == "sortcode");
+
+ delete t;
+}
+
+void MyMoneyInstitutionTest::testEquality () {
+ MyMoneyInstitution t("name", "town", "street", "postcode",
+ "telephone", "manager", "sortcode");
+
+ CPPUNIT_ASSERT(t == *n);
+ t.setStreet("x");
+ CPPUNIT_ASSERT(!(t == *n));
+ t.setStreet("street");
+ CPPUNIT_ASSERT(t == *n);
+ t.setName("x");
+ CPPUNIT_ASSERT(!(t == *n));
+ t.setName("name");
+ CPPUNIT_ASSERT(t == *n);
+ t.setTown("x");
+ CPPUNIT_ASSERT(!(t == *n));
+ t.setTown("town");
+ CPPUNIT_ASSERT(t == *n);
+ t.setPostcode("x");
+ CPPUNIT_ASSERT(!(t == *n));
+ t.setPostcode("postcode");
+ CPPUNIT_ASSERT(t == *n);
+ t.setTelephone("x");
+ CPPUNIT_ASSERT(!(t == *n));
+ t.setTelephone("telephone");
+ CPPUNIT_ASSERT(t == *n);
+ t.setManager("x");
+ CPPUNIT_ASSERT(!(t == *n));
+ t.setManager("manager");
+ CPPUNIT_ASSERT(t == *n);
+
+ MyMoneyInstitution* n1 = new MyMoneyInstitution("GUID1", *n);
+ MyMoneyInstitution* n2 = new MyMoneyInstitution("GUID1", *n);
+
+ n1->addAccountId("A000001");
+ n2->addAccountId("A000001");
+
+ CPPUNIT_ASSERT(*n1 == *n2);
+
+ delete n1;
+ delete n2;
+}
+
+void MyMoneyInstitutionTest::testInequality () {
+ MyMoneyInstitution* n1 = new MyMoneyInstitution("GUID0", *n);
+ MyMoneyInstitution* n2 = new MyMoneyInstitution("GUID1", *n);
+ MyMoneyInstitution* n3 = new MyMoneyInstitution("GUID2", *n);
+ MyMoneyInstitution* n4 = new MyMoneyInstitution("GUID2", *n);
+
+ CPPUNIT_ASSERT(!(*n1 == *n2));
+ CPPUNIT_ASSERT(!(*n1 == *n3));
+ CPPUNIT_ASSERT(*n3 == *n4);
+
+ n3->addAccountId("A000001");
+ n4->addAccountId("A000002");
+ CPPUNIT_ASSERT(!(*n3 == *n4));
+
+ delete n1;
+ delete n2;
+ delete n3;
+ delete n4;
+}
+
+void MyMoneyInstitutionTest::testAccountIDList () {
+ MyMoneyInstitution institution;
+ QStringList list;
+ QString id;
+
+ // list must be empty
+ list = institution.accountList();
+ CPPUNIT_ASSERT(list.count() == 0);
+
+ // add one account
+ institution.addAccountId("A000002");
+ list = institution.accountList();
+ CPPUNIT_ASSERT(list.count() == 1);
+ CPPUNIT_ASSERT(list.contains("A000002") == 1);
+
+ // adding same account shouldn't make a difference
+ institution.addAccountId("A000002");
+ list = institution.accountList();
+ CPPUNIT_ASSERT(list.count() == 1);
+ CPPUNIT_ASSERT(list.contains("A000002") == 1);
+
+ // now add another account
+ institution.addAccountId("A000001");
+ list = institution.accountList();
+ CPPUNIT_ASSERT(list.count() == 2);
+ CPPUNIT_ASSERT(list.contains("A000002") == 1);
+ CPPUNIT_ASSERT(list.contains("A000001") == 1);
+
+ id = institution.removeAccountId("A000001");
+ CPPUNIT_ASSERT(id == "A000001");
+ list = institution.accountList();
+ CPPUNIT_ASSERT(list.count() == 1);
+ CPPUNIT_ASSERT(list.contains("A000002") == 1);
+
+}
+
+void MyMoneyInstitutionTest::testWriteXML() {
+ MyMoneyKeyValueContainer kvp;
+
+ n->addAccountId("A000001");
+ n->addAccountId("A000003");
+ n->setValue(QString("key"), "value");
+
+ QDomDocument doc("TEST");
+ QDomElement el = doc.createElement("INSTITUTION-CONTAINER");
+ doc.appendChild(el);
+
+ MyMoneyInstitution i("I00001", *n);
+
+ i.writeXML(doc, el);
+
+ QString ref = QString(
+ "<!DOCTYPE TEST>\n"
+ "<INSTITUTION-CONTAINER>\n"
+ " <INSTITUTION sortcode=\"sortcode\" id=\"I00001\" manager=\"manager\" name=\"name\" >\n"
+ " <ADDRESS street=\"street\" zip=\"postcode\" city=\"town\" telephone=\"telephone\" />\n"
+ " <ACCOUNTIDS>\n"
+ " <ACCOUNTID id=\"A000001\" />\n"
+ " <ACCOUNTID id=\"A000003\" />\n"
+ " </ACCOUNTIDS>\n"
+ " <KEYVALUEPAIRS>\n"
+ " <PAIR key=\"key\" value=\"value\" />\n"
+ " </KEYVALUEPAIRS>\n"
+ " </INSTITUTION>\n"
+ "</INSTITUTION-CONTAINER>\n");
+
+ CPPUNIT_ASSERT(doc.toString() == ref);
+}
+
+void MyMoneyInstitutionTest::testReadXML() {
+ MyMoneyInstitution i;
+ QString ref_ok = QString(
+ "<!DOCTYPE TEST>\n"
+ "<INSTITUTION-CONTAINER>\n"
+ " <INSTITUTION sortcode=\"sortcode\" id=\"I00001\" manager=\"manager\" name=\"name\" >\n"
+ " <ADDRESS street=\"street\" zip=\"postcode\" city=\"town\" telephone=\"telephone\" />\n"
+ " <ACCOUNTIDS>\n"
+ " <ACCOUNTID id=\"A000001\" />\n"
+ " <ACCOUNTID id=\"A000003\" />\n"
+ " </ACCOUNTIDS>\n"
+ " <KEYVALUEPAIRS>\n"
+ " <PAIR key=\"key\" value=\"value\" />\n"
+ " </KEYVALUEPAIRS>\n"
+ " </INSTITUTION>\n"
+ "</INSTITUTION-CONTAINER>\n");
+
+ QString ref_false = QString(
+ "<!DOCTYPE TEST>\n"
+ "<INSTITUTION-CONTAINER>\n"
+ " <KINSTITUTION sortcode=\"sortcode\" id=\"I00001\" manager=\"manager\" name=\"name\" >\n"
+ " <ADDRESS street=\"street\" zip=\"postcode\" city=\"town\" telephone=\"telephone\" />\n"
+ " <ACCOUNTIDS>\n"
+ " <ACCOUNTID id=\"A000001\" />\n"
+ " <ACCOUNTID id=\"A000003\" />\n"
+ " </ACCOUNTIDS>\n"
+ " </KINSTITUTION>\n"
+ "</INSTITUTION-CONTAINER>\n");
+
+ QDomDocument doc;
+ QDomElement node;
+
+ doc.setContent(ref_false);
+ node = doc.documentElement().firstChild().toElement();
+ try {
+ i = MyMoneyInstitution(node);
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+
+ i.addAccountId("TEST");
+
+ doc.setContent(ref_ok);
+ node = doc.documentElement().firstChild().toElement();
+ try {
+ QStringList alist;
+ alist << "A000001" << "A000003";
+ i = MyMoneyInstitution(node);
+
+ CPPUNIT_ASSERT(i.sortcode() == "sortcode");
+ CPPUNIT_ASSERT(i.id() == "I00001");
+ CPPUNIT_ASSERT(i.manager() == "manager");
+ CPPUNIT_ASSERT(i.name() == "name");
+ CPPUNIT_ASSERT(i.street() == "street");
+ CPPUNIT_ASSERT(i.postcode() == "postcode");
+ CPPUNIT_ASSERT(i.city() == "town");
+ CPPUNIT_ASSERT(i.telephone() == "telephone");
+ CPPUNIT_ASSERT(i.accountList() == alist);
+ CPPUNIT_ASSERT(i.value(QString("key")) == "value");
+
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
diff --git a/kmymoney2/mymoney/mymoneyinstitutiontest.h b/kmymoney2/mymoney/mymoneyinstitutiontest.h
new file mode 100644
index 0000000..8954d6a
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyinstitutiontest.h
@@ -0,0 +1,61 @@
+
+/***************************************************************************
+ mymoneyinstitutiontest.h
+ -------------------
+ copyright : (C) 2002 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 __MYMONEYINSTITUTIONTEST_H__
+#define __MYMONEYINSTITUTIONTEST_H__
+
+#include <cppunit/extensions/HelperMacros.h>
+
+#define private public
+#include "mymoneyinstitution.h"
+#undef private
+
+class MyMoneyInstitutionTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(MyMoneyInstitutionTest);
+ CPPUNIT_TEST(testEmptyConstructor);
+ CPPUNIT_TEST(testSetFunctions);
+ CPPUNIT_TEST(testNonemptyConstructor);
+ CPPUNIT_TEST(testCopyConstructor);
+ CPPUNIT_TEST(testMyMoneyFileConstructor);
+ CPPUNIT_TEST(testEquality);
+ CPPUNIT_TEST(testInequality);
+ CPPUNIT_TEST(testAccountIDList);
+ CPPUNIT_TEST(testWriteXML);
+ CPPUNIT_TEST(testReadXML);
+ CPPUNIT_TEST_SUITE_END();
+
+protected:
+ MyMoneyInstitution *m, *n;
+
+public:
+ MyMoneyInstitutionTest();
+
+ void setUp ();
+ void tearDown ();
+ void testEmptyConstructor();
+ void testSetFunctions();
+ void testNonemptyConstructor();
+ void testCopyConstructor();
+ void testMyMoneyFileConstructor();
+ void testEquality ();
+ void testInequality ();
+ void testAccountIDList ();
+ void testWriteXML();
+ void testReadXML();
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneyinvesttransaction.cpp b/kmymoney2/mymoney/mymoneyinvesttransaction.cpp
new file mode 100644
index 0000000..a7cf082
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyinvesttransaction.cpp
@@ -0,0 +1,42 @@
+/***************************************************************************
+ mymoneyinvesttransaction.cpp - description
+ -------------------
+ begin : Sun Feb 3 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "mymoneyinvesttransaction.h"
+
+#if 0
+MyMoneyInvestTransaction::MyMoneyInvestTransaction(MyMoneyAccount *parent, const long id, transactionMethod method, const QString& number, const QString& memo,
+ const MyMoneyMoney& amount, const QDate& date, const QString& categoryMajor, const QString& categoryMinor, const QString& atmName,
+ const QString& fromTo, const QString& bankFrom, const QString& bankTo, stateE state)
+ : MyMoneyTransaction(parent, id, method, number, memo, amount, date, categoryMajor, categoryMinor, atmName, fromTo, bankFrom, bankTo, state)
+{
+
+}
+
+MyMoneyInvestTransaction::MyMoneyInvestTransaction()
+{
+}
+
+MyMoneyInvestTransaction::~MyMoneyInvestTransaction()
+{
+}
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneyinvesttransaction.h b/kmymoney2/mymoney/mymoneyinvesttransaction.h
new file mode 100644
index 0000000..59f5d5b
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyinvesttransaction.h
@@ -0,0 +1,43 @@
+/***************************************************************************
+ mymoneyinvesttransaction.h - description
+ -------------------
+ begin : Sun Feb 3 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYINVESTTRANSACTION_H
+#define MYMONEYINVESTTRANSACTION_H
+
+#include "mymoneytransaction.h"
+
+#if 0
+/**
+ *@author Kevin Tambascio
+ */
+
+class MyMoneyInvestTransaction : public MyMoneyTransaction {
+public:
+ MyMoneyInvestTransaction();
+ MyMoneyInvestTransaction(MyMoneyAccount *parent, const long id, transactionMethod method, const QString& number, const QString& memo,
+ const MyMoneyMoney& amount, const QDate& date, const QString& categoryMajor, const QString& categoryMinor, const QString& atmName,
+ const QString& fromTo, const QString& bankFrom, const QString& bankTo, stateE state);
+ ~MyMoneyInvestTransaction();
+};
+#endif
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneykeyvaluecontainer.cpp b/kmymoney2/mymoney/mymoneykeyvaluecontainer.cpp
new file mode 100644
index 0000000..4df855b
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneykeyvaluecontainer.cpp
@@ -0,0 +1,120 @@
+/***************************************************************************
+ mymoneykeyvaluecontainer.cpp
+ -------------------
+ begin : Sun Nov 10 2002
+ copyright : (C) 2002-2005 by Thomas Baumgart
+ email : Thomas Baumgart <ipwizard@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include <kmymoney/mymoneykeyvaluecontainer.h>
+#include <kmymoney/mymoneyexception.h>
+
+MyMoneyKeyValueContainer::MyMoneyKeyValueContainer()
+{
+}
+
+MyMoneyKeyValueContainer::MyMoneyKeyValueContainer(const QDomElement& node)
+{
+ if(!node.isNull()) {
+ if("KEYVALUEPAIRS" != node.tagName())
+ throw new MYMONEYEXCEPTION("Node was not KEYVALUEPAIRS");
+
+ m_kvp.clear();
+
+ QDomNodeList nodeList = node.elementsByTagName("PAIR");
+ for(unsigned int i = 0; i < nodeList.count(); ++i) {
+ const QDomElement& el(nodeList.item(i).toElement());
+ m_kvp[el.attribute("key")] = el.attribute("value");
+ }
+ }
+}
+
+MyMoneyKeyValueContainer::~MyMoneyKeyValueContainer()
+{
+}
+
+const QString& MyMoneyKeyValueContainer::value(const QString& key) const
+{
+ QMap<QString, QString>::ConstIterator it;
+
+ it = m_kvp.find(key);
+ if(it != m_kvp.end())
+ return (*it);
+ return QString::null;
+}
+
+void MyMoneyKeyValueContainer::setValue(const QString& key, const QString& value)
+{
+ m_kvp[key] = value;
+}
+
+
+void MyMoneyKeyValueContainer::setPairs(const QMap<QString, QString>& list)
+{
+ m_kvp = list;
+}
+
+void MyMoneyKeyValueContainer::deletePair(const QString& key)
+{
+ QMap<QString, QString>::Iterator it;
+
+ it = m_kvp.find(key);
+ if(it != m_kvp.end())
+ m_kvp.remove(it);
+}
+
+void MyMoneyKeyValueContainer::clear(void)
+{
+ m_kvp.clear();
+}
+
+bool MyMoneyKeyValueContainer::operator == (const MyMoneyKeyValueContainer& right) const
+{
+ QMap<QString, QString>::ConstIterator it_a, it_b;
+
+ it_a = m_kvp.begin();
+ it_b = right.m_kvp.begin();
+
+ while(it_a != m_kvp.end() && it_b != right.m_kvp.end()) {
+ if(it_a.key() != it_b.key()
+ || (((*it_a).length() != 0 || (*it_b).length() != 0) && *it_a != *it_b))
+ return false;
+ ++it_a;
+ ++it_b;
+ }
+
+ return (it_a == m_kvp.end() && it_b == right.m_kvp.end());
+}
+
+void MyMoneyKeyValueContainer::writeXML(QDomDocument& document, QDomElement& parent) const
+{
+ if(m_kvp.count() != 0) {
+ QDomElement el = document.createElement("KEYVALUEPAIRS");
+
+ QMap<QString, QString>::ConstIterator it;
+ for(it = m_kvp.begin(); it != m_kvp.end(); ++it)
+ {
+ QDomElement pair = document.createElement("PAIR");
+ pair.setAttribute("key", it.key());
+ pair.setAttribute("value", it.data());
+ el.appendChild(pair);
+ }
+
+ parent.appendChild(el);
+ }
+}
diff --git a/kmymoney2/mymoney/mymoneykeyvaluecontainer.h b/kmymoney2/mymoney/mymoneykeyvaluecontainer.h
new file mode 100644
index 0000000..fa045b9
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneykeyvaluecontainer.h
@@ -0,0 +1,139 @@
+/***************************************************************************
+ mymoneykeyvaluecontainer.h
+ -------------------
+ begin : Sun Nov 10 2002
+ copyright : (C) 2000-2005 by Thomas Baumgart
+ email : Thomas Baumgart <ipwizard@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYKEYVALUECONTAINER_H
+#define MYMONEYKEYVALUECONTAINER_H
+
+
+/**
+ * @author Thomas Baumgart
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstring.h>
+#include <qmap.h>
+#include <qdom.h>
+#include <kmymoney/export.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+
+/**
+ * This class implements a container for key/value pairs. This is used
+ * to store an arbitrary number of attributes with any of the engine
+ * object. The container can also be used to have attributes that are
+ * attached to this object only for a limited time (e.g. between
+ * start of reconciliation end it's end).
+ *
+ * To give any class the ability to have a key/value pair container,
+ * just derive the class from this one. See MyMoneyAccount as an example.
+ */
+class KMYMONEY_EXPORT MyMoneyKeyValueContainer
+{
+public:
+ MyMoneyKeyValueContainer();
+ MyMoneyKeyValueContainer(const QDomElement& node);
+
+ ~MyMoneyKeyValueContainer();
+
+ /**
+ * This method can be used to retrieve the value for a specific @p key.
+ * If the key is unknown in this container, an empty string will be returned.
+ *
+ * @param key const reference to QString with the key to search for
+ * @return reference to value of this key. If the key does not exist,
+ * an emtpy string is returned.
+ */
+ const QString& value(const QString& key) const;
+
+ /**
+ * This method is used to add a key/value pair to the container or
+ * modify an existing pair.
+ *
+ * @param key const reference to QString with the key to search for
+ * @param value const reference to QString with the value for this key
+ */
+ void setValue(const QString& key, const QString& value);
+
+ /**
+ * This method is used to remove an existing key/value pair from the
+ * container. If the key does not exist, the container is not changed.
+ *
+ * @param key const reference to QString with the key to remove
+ */
+ void deletePair(const QString& key);
+
+ /**
+ * This method clears all pairs currently in the container.
+ */
+ void clear(void);
+
+ /**
+ * This method is used to retrieve the whole set of key/value pairs
+ * from the container. It is meant to be used for permanent storage
+ * functionality.
+ *
+ * @return QMap<QString, QString> containing all key/value pairs of
+ * this container.
+ */
+ const QMap<QString, QString>& pairs(void) const { return m_kvp; };
+
+ /**
+ * This method is used to initially store a set of key/value pairs
+ * in the container. It is meant to be used for loading functionality
+ * from permanent storage.
+ *
+ * @param list const QMap<QString, QString> containing the set of
+ * key/value pairs to be loaded into the container.
+ *
+ * @note All existing key/value pairs in the container will be deleted.
+ */
+ void setPairs(const QMap<QString, QString>& list);
+
+ /**
+ * This operator tests for equality of two MyMoneyKeyValueContainer objects
+ */
+ bool operator == (const MyMoneyKeyValueContainer &) const;
+
+ const QString& operator[] ( const QString& k ) const { return value(k); }
+
+ QString& operator[] ( const QString& k) { return m_kvp[k]; }
+
+ /**
+ * This method creates a QDomElement for the @p document
+ * under the parent node @p parent.
+ *
+ * @param document reference to QDomDocument
+ * @param parent reference to QDomElement parent node
+ */
+ void writeXML(QDomDocument& document, QDomElement& parent) const;
+
+private:
+ /**
+ * This member variable represents the container of key/value pairs.
+ */
+ QMap<QString, QString> m_kvp;
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneykeyvaluecontainertest.cpp b/kmymoney2/mymoney/mymoneykeyvaluecontainertest.cpp
new file mode 100644
index 0000000..6d3d1db
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneykeyvaluecontainertest.cpp
@@ -0,0 +1,189 @@
+/***************************************************************************
+ mymoneykeyvaluecontainertest.cpp
+ -------------------
+ copyright : (C) 2002 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "mymoneykeyvaluecontainertest.h"
+#include <kmymoney/mymoneyexception.h>
+
+MyMoneyKeyValueContainerTest::MyMoneyKeyValueContainerTest()
+{
+}
+
+
+void MyMoneyKeyValueContainerTest::setUp () {
+ m = new MyMoneyKeyValueContainer;
+}
+
+void MyMoneyKeyValueContainerTest::tearDown () {
+ delete m;
+}
+
+void MyMoneyKeyValueContainerTest::testEmptyConstructor() {
+ CPPUNIT_ASSERT(m->m_kvp.count() == 0);
+}
+
+void MyMoneyKeyValueContainerTest::testRetrieveValue() {
+ // load a value into the container
+ m->m_kvp["Key"] = "Value";
+ // make sure it's there
+ CPPUNIT_ASSERT(m->m_kvp.count() == 1);
+ CPPUNIT_ASSERT(m->m_kvp["Key"] == "Value");
+ // now check that the access function works
+ CPPUNIT_ASSERT(m->value("Key") == "Value");
+ CPPUNIT_ASSERT(m->value("key").isEmpty());
+}
+
+void MyMoneyKeyValueContainerTest::testSetValue() {
+ m->setValue("Key", "Value");
+ CPPUNIT_ASSERT(m->m_kvp.count() == 1);
+ CPPUNIT_ASSERT(m->m_kvp["Key"] == "Value");
+}
+
+void MyMoneyKeyValueContainerTest::testDeletePair() {
+ m->setValue("Key", "Value");
+ m->setValue("key", "value");
+ CPPUNIT_ASSERT(m->m_kvp.count() == 2);
+ m->deletePair("Key");
+ CPPUNIT_ASSERT(m->m_kvp.count() == 1);
+ CPPUNIT_ASSERT(m->value("Key").isEmpty());
+ CPPUNIT_ASSERT(m->value("key") == "value");
+}
+
+void MyMoneyKeyValueContainerTest::testClear() {
+ m->setValue("Key", "Value");
+ m->setValue("key", "value");
+ CPPUNIT_ASSERT(m->m_kvp.count() == 2);
+ m->clear();
+ CPPUNIT_ASSERT(m->m_kvp.count() == 0);
+}
+
+void MyMoneyKeyValueContainerTest::testRetrieveList() {
+ QMap<QString, QString> copy;
+
+ copy = m->pairs();
+ CPPUNIT_ASSERT(copy.count() == 0);
+ m->setValue("Key", "Value");
+ m->setValue("key", "value");
+ copy = m->pairs();
+ CPPUNIT_ASSERT(copy.count() == 2);
+ CPPUNIT_ASSERT(copy["Key"] == "Value");
+ CPPUNIT_ASSERT(copy["key"] == "value");
+}
+
+void MyMoneyKeyValueContainerTest::testLoadList() {
+ m->setValue("Key", "Value");
+ m->setValue("key", "value");
+
+ CPPUNIT_ASSERT(m->m_kvp.count() == 2);
+ CPPUNIT_ASSERT(m->m_kvp["Key"] == "Value");
+ CPPUNIT_ASSERT(m->m_kvp["key"] == "value");
+}
+
+void MyMoneyKeyValueContainerTest::testWriteXML() {
+ m->setValue("Key", "Value");
+ m->setValue("key", "value");
+
+ QDomDocument doc("TEST");
+ QDomElement el = doc.createElement("KVP-CONTAINER");
+ doc.appendChild(el);
+ m->writeXML(doc, el);
+
+ QString ref(
+ "<!DOCTYPE TEST>\n"
+ "<KVP-CONTAINER>\n"
+ " <KEYVALUEPAIRS>\n"
+ " <PAIR key=\"Key\" value=\"Value\" />\n"
+ " <PAIR key=\"key\" value=\"value\" />\n"
+ " </KEYVALUEPAIRS>\n"
+ "</KVP-CONTAINER>\n");
+
+ CPPUNIT_ASSERT(doc.toString() == ref);
+}
+
+void MyMoneyKeyValueContainerTest::testReadXML() {
+ m->setValue("Key", "Value");
+ m->setValue("key", "value");
+
+ QString ref_ok(
+ "<!DOCTYPE TEST>\n"
+ "<KVP-CONTAINER>\n"
+ " <KEYVALUEPAIRS>\n"
+ " <PAIR key=\"key\" value=\"Value\" />\n"
+ " <PAIR key=\"Key\" value=\"value\" />\n"
+ " </KEYVALUEPAIRS>\n"
+ "</KVP-CONTAINER>\n");
+
+ QString ref_false(
+ "<!DOCTYPE TEST>\n"
+ "<KVP-CONTAINER>\n"
+ " <KEYVALUE-PAIRS>\n"
+ " <PAIR key=\"key\" value=\"Value\" />\n"
+ " <PAIR key=\"Key\" value=\"value\" />\n"
+ " </KEYVALUE-PAIRS>\n"
+ "</KVP-CONTAINER>\n");
+
+
+ QDomDocument doc;
+ QDomElement node;
+ doc.setContent(ref_false);
+ node = doc.documentElement().firstChild().toElement();
+
+ // make sure, an empty node does not trigger an exception
+ try {
+ MyMoneyKeyValueContainer k(QDomNode());
+ } catch(MyMoneyException *e) {
+ CPPUNIT_FAIL("Unexpected exception");
+ delete e;
+ }
+
+ try {
+ MyMoneyKeyValueContainer k(node);
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+
+ doc.setContent(ref_ok);
+ node = doc.documentElement().firstChild().toElement();
+ try {
+ MyMoneyKeyValueContainer k(node);
+ CPPUNIT_ASSERT(k.m_kvp.count() == 2);
+ CPPUNIT_ASSERT(k.value("key") == "Value");
+ CPPUNIT_ASSERT(k.value("Key") == "value");
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneyKeyValueContainerTest::testArrayRead()
+{
+ MyMoneyKeyValueContainer kvp;
+ const MyMoneyKeyValueContainer& ckvp = kvp;
+ CPPUNIT_ASSERT(kvp.pairs().count() == 0);
+ CPPUNIT_ASSERT(ckvp["Key"].isEmpty());
+ CPPUNIT_ASSERT(kvp.pairs().count() == 0);
+ kvp.setValue("Key", "Value");
+ CPPUNIT_ASSERT(kvp["Key"] == "Value");
+}
+
+void MyMoneyKeyValueContainerTest::testArrayWrite()
+{
+ MyMoneyKeyValueContainer kvp;
+ kvp["Key"] = "Value";
+ CPPUNIT_ASSERT(kvp.pairs().count() == 1);
+ CPPUNIT_ASSERT(kvp.value("Key") == "Value");
+}
+
diff --git a/kmymoney2/mymoney/mymoneykeyvaluecontainertest.h b/kmymoney2/mymoney/mymoneykeyvaluecontainertest.h
new file mode 100644
index 0000000..d2d89d8
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneykeyvaluecontainertest.h
@@ -0,0 +1,61 @@
+/***************************************************************************
+ mymoneykeyvaluecontainertest.h
+ -------------------
+ copyright : (C) 2002 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 __MYMONEYKEYVALUECONTAINERTEST_H__
+#define __MYMONEYKEYVALUECONTAINERTEST_H__
+
+#include <cppunit/extensions/HelperMacros.h>
+
+#define private public
+#include "mymoneykeyvaluecontainer.h"
+#undef private
+
+class MyMoneyKeyValueContainerTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(MyMoneyKeyValueContainerTest);
+ CPPUNIT_TEST(testEmptyConstructor);
+ CPPUNIT_TEST(testRetrieveValue);
+ CPPUNIT_TEST(testSetValue);
+ CPPUNIT_TEST(testDeletePair);
+ CPPUNIT_TEST(testClear);
+ CPPUNIT_TEST(testRetrieveList);
+ CPPUNIT_TEST(testLoadList);
+ CPPUNIT_TEST(testWriteXML);
+ CPPUNIT_TEST(testReadXML);
+ CPPUNIT_TEST(testArrayRead);
+ CPPUNIT_TEST(testArrayWrite);
+ CPPUNIT_TEST_SUITE_END();
+
+protected:
+ MyMoneyKeyValueContainer *m;
+
+public:
+ MyMoneyKeyValueContainerTest();
+ void setUp ();
+ void tearDown ();
+ void testEmptyConstructor();
+ void testRetrieveValue();
+ void testSetValue();
+ void testDeletePair();
+ void testClear();
+ void testRetrieveList();
+ void testLoadList();
+ void testArrayRead();
+ void testArrayWrite();
+ void testWriteXML();
+ void testReadXML();
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneymoney.cpp b/kmymoney2/mymoney/mymoneymoney.cpp
new file mode 100644
index 0000000..993a872
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneymoney.cpp
@@ -0,0 +1,794 @@
+/***************************************************************************
+ mymoneymymoney.cpp - description
+ -------------------
+ begin : Thu Feb 21 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+// make sure, that this is defined before we even include any other header file
+#ifndef __STDC_LIMIT_MACROS
+ #define __STDC_LIMIT_MACROS // force definition of min and max values
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qregexp.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "mymoneymoney.h"
+#include "mymoneyaccount.h"
+#include "mymoneysecurity.h"
+
+unsigned char MyMoneyMoney::_thousandSeparator = ',';
+unsigned char MyMoneyMoney::_decimalSeparator = '.';
+MyMoneyMoney::signPosition MyMoneyMoney::_negativeMonetarySignPosition = BeforeQuantityMoney;
+MyMoneyMoney::signPosition MyMoneyMoney::_positiveMonetarySignPosition = BeforeQuantityMoney;
+bool MyMoneyMoney::_negativePrefixCurrencySymbol = false;
+bool MyMoneyMoney::_positivePrefixCurrencySymbol = false;
+
+MyMoneyMoney::fileVersionE MyMoneyMoney::_fileVersion = MyMoneyMoney::FILE_4_BYTE_VALUE;
+
+MyMoneyMoney MyMoneyMoney::maxValue = MyMoneyMoney(INT64_MAX,100);
+MyMoneyMoney MyMoneyMoney::minValue = MyMoneyMoney(INT64_MIN,100);
+MyMoneyMoney MyMoneyMoney::autoCalc = MyMoneyMoney(INT64_MIN+1,100);
+
+void MyMoneyMoney::setNegativePrefixCurrencySymbol(const bool flag)
+{
+ _negativePrefixCurrencySymbol = flag;
+}
+
+void MyMoneyMoney::setPositivePrefixCurrencySymbol(const bool flag)
+{
+ _positivePrefixCurrencySymbol = flag;
+}
+
+void MyMoneyMoney::setNegativeMonetarySignPosition(const signPosition pos)
+{
+ _negativeMonetarySignPosition = pos;
+}
+
+MyMoneyMoney::signPosition MyMoneyMoney::negativeMonetarySignPosition(void)
+{
+ return _negativeMonetarySignPosition;
+}
+
+void MyMoneyMoney::setPositiveMonetarySignPosition(const signPosition pos)
+{
+ _positiveMonetarySignPosition = pos;
+}
+
+MyMoneyMoney::signPosition MyMoneyMoney::positiveMonetarySignPosition(void)
+{
+ return _positiveMonetarySignPosition;
+}
+
+void MyMoneyMoney::setThousandSeparator(const unsigned char separator)
+{
+ if(separator != ' ')
+ _thousandSeparator = separator;
+ else
+ _thousandSeparator = 0;
+}
+
+unsigned char MyMoneyMoney::thousandSeparator(void)
+{
+ return _thousandSeparator;
+}
+
+void MyMoneyMoney::setDecimalSeparator(const unsigned char separator)
+{
+ if(separator != ' ')
+ _decimalSeparator = separator;
+ else
+ _decimalSeparator = 0;
+}
+
+unsigned char MyMoneyMoney::decimalSeparator(void)
+{
+ return _decimalSeparator;
+}
+
+void MyMoneyMoney::setFileVersion(fileVersionE version)
+{
+ _fileVersion = version;
+}
+
+MyMoneyMoney::MyMoneyMoney(const QString& pszAmount)
+{
+ m_num = 0;
+ m_denom = 1;
+
+ // an empty string is zero
+ if (pszAmount.isEmpty())
+ return;
+
+ // take care of prices given in the form "8 5/16"
+ // and our own internal represenation
+ QRegExp regExp("^((\\d+)\\s+|-)?(\\d+)/(\\d+)");
+ // +-#2-+ +-#3-+ +-#4-+
+ // +-----#1-----+
+ if (regExp.search(pszAmount) > -1) {
+ m_num = regExp.cap(3).toLongLong();
+ m_denom = regExp.cap(4).toLongLong();
+ const QString& part1 = regExp.cap(1);
+ if(!part1.isEmpty()) {
+ if(part1 == QString("-")) {
+ m_num = -m_num;
+
+ } else {
+ *this += MyMoneyMoney(regExp.cap(2));
+ }
+ }
+ return;
+ }
+
+ QString res = pszAmount;
+ // get rid of anything that is not
+ // a) numeric
+ // b) _decimalSeparator
+ // c) negative indicator
+ QString validChars = QString("\\d%1").arg(QChar(decimalSeparator()));
+ // we need to escape the minus sign here, because later on it will be
+ // part of "\d,-()" and that does not work. It needs to be "\d,\-()"
+ // And we need two of them, because we're in C
+ QString negChars("\\-");
+ if(_negativeMonetarySignPosition == ParensAround) {
+ // Since we want to allow '-' as well as '()' for negative entry
+ // we just add the parens here.
+ negChars += "()";
+ }
+ validChars += negChars;
+ // qDebug("0: '%s'", validChars.data());
+
+ QRegExp invChars(QString("[^%1]").arg(validChars));
+ // qDebug("1: '%s'", res.data());
+ res.remove(invChars);
+
+ QRegExp negCharSet(QString("[%1]").arg(negChars));
+ bool isNegative = false;
+ if(res.find(negCharSet) != -1) {
+ isNegative = true;
+ res.remove(negCharSet);
+ }
+ // qDebug("2: '%s' %s", res.data(), isNegative ? "(-)" : "");
+ int pos;
+
+ // qDebug("3: '%s'", res.data());
+ if((pos = res.find(_decimalSeparator)) != -1) {
+ // make sure, we get the denominator right
+ m_denom = precToDenom(res.length() - pos - 1);
+
+ // now remove the decimal symbol
+ res.remove(pos, 1);
+ }
+ // qDebug("4: '%s'", res.data());
+ if(res.length() > 0)
+ m_num = atoll( res );
+
+ if(isNegative)
+ m_num = -m_num;
+}
+
+QString MyMoneyMoney::formatMoney(int denom, bool showThousandSeparator) const
+{
+ return formatMoney("", denomToPrec(denom), showThousandSeparator);
+}
+
+QString MyMoneyMoney::formatMoney(const MyMoneyAccount& acc, const MyMoneySecurity& sec, bool showThousandSeparator) const
+{
+ return formatMoney(sec.tradingSymbol(), denomToPrec(acc.fraction()), showThousandSeparator);
+}
+
+QString MyMoneyMoney::formatMoney(const MyMoneySecurity& sec, bool showThousandSeparator) const
+{
+ return formatMoney(sec.tradingSymbol(), denomToPrec(sec.smallestAccountFraction()), showThousandSeparator);
+}
+
+QString MyMoneyMoney::formatMoney(const QString& currency, const int prec, bool showThousandSeparator) const
+{
+ QString res;
+ QString tmpCurrency = currency;
+ int tmpPrec = prec;
+ signed64 denom = 1;
+ signed64 m_64Value;
+
+ // if prec == -1 we want the maximum possible but w/o trailing zeroes
+ if(tmpPrec > -1) {
+ while(tmpPrec--) {
+ denom *= 10;
+ }
+ } else {
+ // fix it to a max of 8 digits on the right side for now
+ denom = 100000000;
+ }
+
+ m_64Value = convert(denom).m_num;
+
+ // Once we really support multiple currencies then this method will
+ // be much better than using KGlobal::locale()->formatMoney.
+ bool bNegative = false;
+ signed64 left = m_64Value / denom;
+ signed64 right = m_64Value % denom;
+
+ if (right < 0){
+ right = -right;
+ bNegative = true;
+ }
+ if (left < 0) {
+ left = -left;
+ bNegative = true;
+ }
+
+ if(left & 0xFFFFFFFF00000000LL) {
+ signed64 tmp = left;
+
+ // QString.sprintf("%Ld") did not work :-(, so I had to
+ // do it the old ugly way.
+ while(tmp) {
+ res.insert(0, QString("%1").arg(static_cast<int>(tmp % 10)));
+ tmp /= 10;
+ }
+
+ } else
+ res = QString("%1").arg((long)left);
+
+ if(showThousandSeparator) {
+ int pos = res.length();
+ while((0 < (pos -= 3)) && thousandSeparator())
+ res.insert(pos, thousandSeparator());
+ }
+
+ if(prec > 0 || (prec == -1 && right != 0)) {
+ if(decimalSeparator())
+ res += decimalSeparator();
+
+ // using
+ //
+ // res += QString("%1").arg(right).rightJustify(prec, '0', true);
+ //
+ // caused some weird results if right was rather large. Eg: right being
+ // 666600000 should have appended a 0, but instead it prepended a 0. With
+ // res being "2," the result wasn't "2,6666000000" as expected, but rather
+ // "2,0666600000" which was not usable. The code below works for me.
+ QString rs = QString("%1").arg(right);
+ if(prec != -1)
+ rs = rs.rightJustify(prec, '0', true);
+ else {
+ rs = rs.rightJustify(8, '0', true);
+ // no trailing zeroes or decimal separators
+ while(rs.endsWith("0"))
+ rs.truncate(rs.length()-1);
+ while(rs.endsWith(QChar(decimalSeparator())))
+ rs.truncate(rs.length()-1);
+ }
+ res += rs;
+ }
+
+ signPosition signpos = bNegative ? _negativeMonetarySignPosition : _positiveMonetarySignPosition;
+ QString sign = bNegative ? "-" : "";
+
+ switch(signpos) {
+ case ParensAround:
+ res.prepend('(');
+ res.append(')');
+ break;
+ case BeforeQuantityMoney:
+ res.prepend(sign);
+ break;
+ case AfterQuantityMoney:
+ res.append(sign);
+ break;
+ case BeforeMoney:
+ tmpCurrency.prepend(sign);
+ break;
+ case AfterMoney:
+ tmpCurrency.append(sign);
+ break;
+ }
+ if(!tmpCurrency.isEmpty()) {
+ if(bNegative ? _negativePrefixCurrencySymbol : _positivePrefixCurrencySymbol){
+ res.prepend(' ');
+ res.prepend(tmpCurrency);
+ } else {
+ res.append(' ');
+ res.append(tmpCurrency);
+ }
+ }
+
+ return res;
+}
+
+const QString MyMoneyMoney::toString(void) const
+{
+ signed64 tmp = m_num < 0 ? - m_num : m_num;
+ QString res;
+ QString resf;
+
+ // QString.sprintf("%Ld") did not work :-(, so I had to
+ // do it the old ugly way.
+ while(tmp) {
+ res.prepend(QString("%1").arg(static_cast<int>(tmp % 10)));
+ tmp /= 10;
+ }
+ if(res.isEmpty())
+ res = QString("0");
+
+ if(m_num < 0)
+ res.prepend('-');
+
+ tmp = m_denom;
+ while(tmp) {
+ resf.prepend(QString("%1").arg(static_cast<int>(tmp % 10)));
+ tmp /= 10;
+ }
+ return res + "/" + resf;
+}
+
+QDataStream &operator<<(QDataStream &s, const MyMoneyMoney &_money)
+{
+ // We WILL lose data here if the user has more than 2 billion pounds :-(
+ // QT defined it here as long:
+ // qglobal.h:typedef long Q_INT64;
+
+ MyMoneyMoney money = _money.convert(100);
+
+ switch(MyMoneyMoney::_fileVersion) {
+ case MyMoneyMoney::FILE_4_BYTE_VALUE:
+ if(money.m_num & 0xffffffff00000000LL)
+ qWarning("Lost data while writing out MyMoneyMoney object using deprecated 4 byte writer");
+
+ s << static_cast<Q_INT32> (money.m_num & 0xffffffff);
+ break;
+
+ default:
+ qDebug("Unknown file version while writing MyMoneyMoney object! Use FILE_8_BYTE_VALUE");
+ // tricky fall through here
+
+ case MyMoneyMoney::FILE_8_BYTE_VALUE:
+ s << static_cast<Q_INT32> (money.m_num >> 32);
+ s << static_cast<Q_INT32> (money.m_num & 0xffffffff);
+ break;
+ }
+ return s;
+}
+
+QDataStream &operator>>(QDataStream &s, MyMoneyMoney &money)
+{
+ Q_INT32 tmp;
+ switch(MyMoneyMoney::_fileVersion) {
+ case MyMoneyMoney::FILE_4_BYTE_VALUE:
+ s >> tmp;
+ money.m_num = static_cast<signed64> (tmp);
+ money.m_denom = 100;
+ break;
+
+ default:
+ qDebug("Unknown file version while writing MyMoneyMoney object! FILE_8_BYTE_VALUE assumed");
+ // tricky fall through here
+
+ case MyMoneyMoney::FILE_8_BYTE_VALUE:
+ s >> tmp;
+ money.m_num = static_cast<signed64> (tmp);
+ money.m_num <<= 32;
+ s >> tmp;
+ money.m_num |= static_cast<signed64> (tmp);
+ money.m_denom = 100;
+ break;
+ }
+ return s;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: operator+
+// Purpose: Addition operator - adds the input amount to the object
+// Returns: The current object
+// Throws: Nothing.
+// Arguments: b - MyMoneyMoney object to be added
+//
+////////////////////////////////////////////////////////////////////////////////
+MyMoneyMoney MyMoneyMoney::operator+( const MyMoneyMoney& _b) const
+{
+ MyMoneyMoney a(*this);
+ MyMoneyMoney b(_b);
+ MyMoneyMoney sum;
+ signed64 lcd;
+
+ if(a.m_denom < 0) {
+ a.m_num *= a.m_denom;
+ a.m_denom = 1;
+ }
+ if(b.m_denom < 0) {
+ b.m_num *= b.m_denom;
+ b.m_denom = 1;
+ }
+
+ if(a.m_denom == b.m_denom) {
+ sum.m_num = a.m_num + b.m_num;
+ sum.m_denom = a.m_denom;
+ } else {
+ lcd = a.getLcd(b);
+ sum.m_num = a.m_num*(lcd/a.m_denom) + b.m_num*(lcd/b.m_denom);
+ sum.m_denom = lcd;
+ }
+ return sum;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: operator-
+// Purpose: Addition operator - subtracts the input amount from the object
+// Returns: The current object
+// Throws: Nothing.
+// Arguments: AmountInPence - MyMoneyMoney object to be subtracted
+//
+////////////////////////////////////////////////////////////////////////////////
+MyMoneyMoney MyMoneyMoney::operator-( const MyMoneyMoney& _b) const
+{
+ MyMoneyMoney a(*this);
+ MyMoneyMoney b(_b);
+ MyMoneyMoney diff;
+ signed64 lcd;
+
+ if(a.m_denom < 0) {
+ a.m_num *= a.m_denom;
+ a.m_denom = 1;
+ }
+ if(b.m_denom < 0) {
+ b.m_num *= b.m_denom;
+ b.m_denom = 1;
+ }
+
+ if(a.m_denom == b.m_denom) {
+ diff.m_num = a.m_num - b.m_num;
+ diff.m_denom = a.m_denom;
+ } else {
+ lcd = a.getLcd(b);
+ diff.m_num = a.m_num*(lcd/a.m_denom) - b.m_num*(lcd/b.m_denom);
+ diff.m_denom = lcd;
+ }
+ return diff;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: operator*
+// Purpose: Multiplication operator - multiplies the input amount to the object
+// Returns: The current object
+// Throws: Nothing.
+// Arguments: b - MyMoneyMoney object to be multiplied
+//
+////////////////////////////////////////////////////////////////////////////////
+MyMoneyMoney MyMoneyMoney::operator*( const MyMoneyMoney& _b ) const
+{
+ MyMoneyMoney a(*this);
+ MyMoneyMoney b(_b);
+ MyMoneyMoney product;
+
+ if(a.m_denom < 0) {
+ a.m_num *= a.m_denom;
+ a.m_denom = 1;
+ }
+ if(b.m_denom < 0) {
+ b.m_num *= b.m_denom;
+ b.m_denom = 1;
+ }
+
+ product.m_num = a.m_num * b.m_num;
+ product.m_denom = a.m_denom * b.m_denom;
+
+ if(product.m_denom < 0) {
+ product.m_num = -product.m_num;
+ product.m_denom = -product.m_denom;
+ }
+ return product;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: operator/
+// Purpose: Division operator - divides the object by the input amount
+// Returns: The current object
+// Throws: Nothing.
+// Arguments: b - MyMoneyMoney object to be used as dividend
+//
+////////////////////////////////////////////////////////////////////////////////
+MyMoneyMoney MyMoneyMoney::operator / ( const MyMoneyMoney& _b ) const
+{
+ MyMoneyMoney a(*this);
+ MyMoneyMoney b(_b);
+ MyMoneyMoney quotient;
+ signed64 lcd;
+
+ if(a.m_denom < 0) {
+ a.m_num *= a.m_denom;
+ a.m_denom = 1;
+ }
+ if(b.m_denom < 0) {
+ b.m_num *= b.m_denom;
+ b.m_denom = 1;
+ }
+
+ if(a.m_denom == b.m_denom) {
+ quotient.m_num = a.m_num;
+ quotient.m_denom = b.m_num;
+ }
+ else {
+ /* ok, convert to the lcd and compute from there... */
+ lcd = a.getLcd(b);
+ quotient.m_num = a.m_num*(lcd/a.m_denom);
+ quotient.m_denom = b.m_num*(lcd/b.m_denom);
+ }
+
+ if(quotient.m_denom < 0) {
+ quotient.m_num = -quotient.m_num;
+ quotient.m_denom = -quotient.m_denom;
+ }
+
+ Q_ASSERT(quotient.m_denom != 0);
+
+ return quotient;
+}
+
+signed64 MyMoneyMoney::getLcd(const MyMoneyMoney& b) const
+{
+ signed64 current_divisor = 2;
+ signed64 max_square;
+ signed64 three_count = 0;
+ signed64 small_denom;
+ signed64 big_denom;
+
+ if(b.m_denom < m_denom) {
+ small_denom = b.m_denom;
+ big_denom = m_denom;
+ }
+ else {
+ small_denom = m_denom;
+ big_denom = b.m_denom;
+ }
+
+ /* special case: smaller divides smoothly into larger */
+ if((big_denom % small_denom) == 0) {
+ return big_denom;
+ }
+
+ max_square = small_denom;
+
+ /* the LCM algorithm : factor out the union of the prime factors of the
+ * two args and then multiply the remainders together.
+ *
+ * To do this, we find the successive prime factors of the smaller
+ * denominator and eliminate them from both the smaller and larger
+ * denominator (so we only count factors on a one-on-one basis),
+ * then multiply the original smaller by the remains of the larger.
+ *
+ * I.e. LCM 100,96875 == 2*2*5*5,31*5*5*5*5 = 2*2,31*5*5
+ * answer: multiply 100 by 31*5*5 == 387500
+ */
+ while((current_divisor * current_divisor) <= max_square) {
+ if(((small_denom % current_divisor) == 0) &&
+ ((big_denom % current_divisor) == 0)) {
+ big_denom = big_denom / current_divisor;
+ small_denom = small_denom / current_divisor;
+ }
+ else {
+ if(current_divisor == 2) {
+ current_divisor++;
+ }
+ else if(three_count == 3) {
+ current_divisor += 4;
+ three_count = 1;
+ }
+ else {
+ current_divisor += 2;
+ three_count++;
+ }
+ }
+
+ if((current_divisor > small_denom) ||
+ (current_divisor > big_denom)) {
+ break;
+ }
+ }
+
+ /* max_sqaure is the original small_denom */
+ return max_square * big_denom;
+}
+
+const MyMoneyMoney MyMoneyMoney::convert(const signed64 _denom, const roundingMethod how) const
+{
+ MyMoneyMoney out(*this);
+ MyMoneyMoney in (*this);
+ MyMoneyMoney temp;
+
+ signed64 denom = _denom;
+ signed64 temp_bc;
+ signed64 temp_a;
+ signed64 remainder;
+ signed64 sign;
+ int denom_neg=0;
+
+ if(m_denom != denom) {
+ /* if the denominator of the input value is negative, get rid of that. */
+ if(m_denom < 0) {
+ in.m_num = in.m_num * (- in.m_denom);
+ in.m_denom = 1;
+ }
+
+ sign = (in.m_num < 0) ? -1 : 1;
+
+ /* if the denominator is less than zero, we are to interpret it as
+ * the reciprocal of its magnitude. */
+ if(denom < 0) {
+ denom = - denom;
+ denom_neg = 1;
+ temp_a = (in.m_num < 0) ? -in.m_num : in.m_num;
+ temp_bc = in.m_denom * denom;
+ remainder = in.m_num % temp_bc;
+ out.m_num = in.m_num / temp_bc;
+ out.m_denom = -denom;
+ }
+ else {
+ /* do all the modulo and int division on positive values to make
+ * things a little clearer. Reduce the fraction denom/in.denom to
+ * help with range errors (FIXME : need bigger intermediate rep) */
+ temp.m_num = denom;
+ temp.m_denom = in.m_denom;
+ temp = temp.reduce();
+
+ out.m_num = in.m_num * temp.m_num;
+ out.m_num = (out.m_num < 0) ? -out.m_num : out.m_num;
+ remainder = out.m_num % temp.m_denom;
+ out.m_num = out.m_num / temp.m_denom;
+ out.m_denom = denom;
+ }
+
+ if(remainder > 0) {
+ switch(how) {
+ case RndFloor:
+ if(sign < 0) {
+ out.m_num = out.m_num + 1;
+ }
+ break;
+
+ case RndCeil:
+ if(sign > 0) {
+ out.m_num = out.m_num + 1;
+ }
+ break;
+
+ case RndTrunc:
+ break;
+
+ case RndPromote:
+ out.m_num = out.m_num + 1;
+ break;
+
+ case RndHalfDown:
+ if(denom_neg) {
+ if((2 * remainder) > in.m_denom*denom) {
+ out.m_num = out.m_num + 1;
+ }
+ }
+ else if((2 * remainder) > temp.m_denom) {
+ out.m_num = out.m_num + 1;
+ }
+ break;
+
+ case RndHalfUp:
+ if(denom_neg) {
+ if((2 * remainder) >= in.m_denom*denom) {
+ out.m_num = out.m_num + 1;
+ }
+ }
+ else if((2 * remainder ) >= temp.m_denom) {
+ out.m_num = out.m_num + 1;
+ }
+ break;
+
+ case RndRound:
+ if(denom_neg) {
+ if((2 * remainder) > in.m_denom*denom) {
+ out.m_num = out.m_num + 1;
+ }
+ else if((2 * remainder) == in.m_denom*denom) {
+ if(out.m_num % 2) {
+ out.m_num = out.m_num + 1;
+ }
+ }
+ }
+ else {
+ if((2 * remainder ) > temp.m_denom) {
+ out.m_num = out.m_num + 1;
+ }
+ else if((2 * remainder) == temp.m_denom) {
+ if(out.m_num % 2) {
+ out.m_num = out.m_num + 1;
+ }
+ }
+ }
+ break;
+
+ case RndNever:
+ qWarning("MyMoneyMoney: have remainder \"%Ld/%Ld\"->convert(%Ld, %d)",
+ m_num, m_denom, _denom, how);
+ break;
+ }
+ }
+ out.m_num = (sign > 0) ? out.m_num : (-out.m_num);
+ }
+
+ return out;
+}
+
+/********************************************************************
+ * gnc_numeric_reduce
+ * reduce a fraction by GCF elimination. This is NOT done as a
+ * part of the arithmetic API unless GNC_DENOM_REDUCE is specified
+ * as the output denominator.
+ ********************************************************************/
+const MyMoneyMoney MyMoneyMoney::reduce(void) const
+{
+ MyMoneyMoney out;
+ signed64 t;
+ signed64 num = (m_num < 0) ? (- m_num) : m_num ;
+ signed64 denom = m_denom;
+
+ /* the strategy is to use euclid's algorithm */
+ while (denom > 0) {
+ t = num % denom;
+ num = denom;
+ denom = t;
+ }
+ /* num = gcd */
+
+ /* all calculations are done on positive num, since it's not
+ * well defined what % does for negative values */
+ out.m_num = m_num / num;
+ out.m_denom = m_denom / num;
+ return out;
+}
+
+signed64 MyMoneyMoney::precToDenom(int prec)
+{
+ signed64 denom = 1;
+
+ while(prec--)
+ denom *= 10;
+
+ return denom;
+}
+
+double MyMoneyMoney::toDouble(void) const
+{
+ return static_cast<double>(m_num) / static_cast<double>(m_denom);
+}
+
+int MyMoneyMoney::denomToPrec(signed64 fract)
+{
+ int rc = 0;
+ while(fract > 1) {
+ rc++;
+ fract /= 10;
+ }
+ return rc;
+}
+
+MyMoneyMoney::operator int() const
+{
+ return static_cast<int> (m_num / m_denom);
+}
diff --git a/kmymoney2/mymoney/mymoneymoney.h b/kmymoney2/mymoney/mymoneymoney.h
new file mode 100644
index 0000000..74235c7
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneymoney.h
@@ -0,0 +1,612 @@
+/***************************************************************************
+ mymoneymoney.h
+ -------------------
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 _MYMONEYMONEY_H
+#define _MYMONEYMONEY_H
+
+#include <stdlib.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifndef HAVE_ATOLL
+# ifdef HAVE_STRTOLL
+# define atoll(a) strtoll(a, 0, 10)
+# endif
+#endif
+
+#include <cmath>
+
+#ifdef _GLIBCPP_HAVE_MODFL
+#define HAVE_LONG_DOUBLE 1
+#endif
+
+#ifndef HAVE_LONG_DOUBLE
+#define HAVE_LONG_DOUBLE 0
+#endif
+
+// So we can save this object
+#include <qstring.h>
+#include <qdatastream.h>
+#include <kmymoney/export.h>
+#include <kmymoney/mymoneyexception.h>
+
+// Check for standard definitions
+#ifdef HAVE_STDINT_H
+ #ifndef __STDC_LIMIT_MACROS
+ #define __STDC_LIMIT_MACROS // force definition of min and max values
+ #endif
+ #include <stdint.h>
+#else
+ #include <limits.h>
+ #define INT64_MAX LLONG_MAX
+ #define INT64_MIN LLONG_MIN
+#endif
+
+typedef int64_t signed64;
+typedef uint64_t unsigned64;
+
+class MyMoneyAccount;
+class MyMoneySecurity;
+
+/**
+ * This class represents a value within the MyMoney Engine
+ *
+ * @author Michael Edwardes
+ */
+class KMYMONEY_EXPORT MyMoneyMoney
+{
+public:
+ enum fileVersionE {
+ FILE_4_BYTE_VALUE = 0,
+ FILE_8_BYTE_VALUE
+ };
+
+ enum signPosition {
+ // keep those in sync with the ones defined in klocale.h
+ ParensAround = 0,
+ BeforeQuantityMoney = 1,
+ AfterQuantityMoney = 2,
+ BeforeMoney = 3,
+ AfterMoney = 4
+ };
+
+ enum roundingMethod {
+ RndNever = 0,
+ RndFloor,
+ RndCeil,
+ RndTrunc,
+ RndPromote,
+ RndHalfDown,
+ RndHalfUp,
+ RndRound
+ };
+
+ // construction
+ MyMoneyMoney();
+ MyMoneyMoney( const int iAmount, const signed64 denom = 100 );
+ MyMoneyMoney( const QString& pszAmount );
+ MyMoneyMoney( const signed64 Amount, const signed64 denom = 100 );
+ MyMoneyMoney( const double dAmount, const signed64 denom = 100 );
+#if HAVE_LONG_DOUBLE
+ MyMoneyMoney( const long double dAmount, const signed64 denom = 100 );
+#endif
+
+ // copy constructor
+ MyMoneyMoney( const MyMoneyMoney& AmountInPence );
+
+ // signed64 value(const int prec = 2) const;
+ const MyMoneyMoney abs(void) const { return m_num < 0 ? -(*this) : *this; };
+
+ /**
+ * This method returns a formatted string according to the settings
+ * of _thousandSeparator, _decimalSeparator, _negativeMonetarySignPosition,
+ * _positiveMonetaryPosition, _negativePrefixCurrencySymbol and
+ * _positivePrefixCurrencySymbol. Those values can be modified using
+ * the appropriate set-methods.
+ *
+ * @param currency The currency symbol
+ * @param prec The number of fractional digits
+ * @param showThousandSeparator should the thousandSeparator symbol be inserted
+ * (@a true) or not (@a false) (default true)
+ */
+ QString formatMoney(const QString& currency, const int prec, bool showThousandSeparator = true) const;
+
+ /**
+ * This is a convenience method. It behaves exactly as the above one, but takes the information
+ * about currency symbol and precision out of the MyMoneySecurity and MyMoneyAccount objects
+ * @a acc and @a sec.
+ */
+ QString formatMoney(const MyMoneyAccount& acc, const MyMoneySecurity& sec, bool showThousandSeparator = true) const;
+
+ /**
+ * This is a convenience method. It behaves exactly as the above one, but takes the information
+ * about currency symbol and precision out of the MyMoneySecurity object @a sec.
+ */
+ QString formatMoney(const MyMoneySecurity& sec, bool showThousandSeparator = true) const;
+
+ /**
+ * This is a convenience method. It behaves exactly as the above one, but takes the information
+ * about precision out of the denomination @a denom. No currency symbol is shown. If you want
+ * to see a currency symbol, please use formatMoney(const MyMoneyAccount& acc, const MyMoneySecurity& sec, bool showThousandSeparator)
+ * instead.
+ *
+ * @note denom is often set to account.fraction(security).
+ */
+ QString formatMoney(int denom, bool showThousandSeparator = true) const;
+
+ /**
+ * This method is used to convert the smallest fraction information into
+ * the corresponding number of digits used for precision.
+ *
+ * @param fract smallest fractional part (e.g. 100 for cents)
+ * @return number of precision digits (e.g. 2 for cents)
+ */
+ static int denomToPrec(signed64 fract);
+
+ const QString toString(void) const;
+ const MyMoneyMoney convert(const signed64 denom = 100, const roundingMethod how = RndRound) const;
+ static signed64 precToDenom(int prec);
+ double toDouble(void) const;
+
+ static void setThousandSeparator(const unsigned char);
+ static void setDecimalSeparator(const unsigned char);
+ static void setNegativeMonetarySignPosition(const signPosition pos);
+ static void setPositiveMonetarySignPosition(const signPosition pos);
+ static void setNegativePrefixCurrencySymbol(const bool flags);
+ static void setPositivePrefixCurrencySymbol(const bool flags);
+
+ static unsigned char thousandSeparator(void);
+ static unsigned char decimalSeparator(void);
+ static signPosition negativeMonetarySignPosition(void);
+ static signPosition positiveMonetarySignPosition(void);
+ static void setFileVersion(const fileVersionE version);
+
+ // assignment
+ const MyMoneyMoney& operator=( const MyMoneyMoney& Amount );
+ const MyMoneyMoney& operator=( const QString& pszAmount );
+
+ // comparison
+ bool operator==( const MyMoneyMoney& Amount ) const;
+ bool operator!=( const MyMoneyMoney& Amount ) const;
+ bool operator<( const MyMoneyMoney& Amount ) const;
+ bool operator>( const MyMoneyMoney& Amount ) const;
+ bool operator<=( const MyMoneyMoney& Amount ) const;
+ bool operator>=( const MyMoneyMoney& Amount ) const;
+
+ bool operator==( const QString& pszAmount ) const;
+ bool operator!=( const QString& pszAmount ) const;
+ bool operator<( const QString& pszAmount ) const;
+ bool operator>( const QString& pszAmount ) const;
+ bool operator<=( const QString& pszAmount ) const;
+ bool operator>=( const QString& pszAmount ) const;
+
+ // calculation
+ MyMoneyMoney operator+( const MyMoneyMoney& Amount ) const;
+
+ MyMoneyMoney operator-( const MyMoneyMoney& Amount ) const;
+ MyMoneyMoney operator-( ) const;
+
+ MyMoneyMoney operator*( const MyMoneyMoney& factor ) const;
+ MyMoneyMoney operator*( int factor ) const;
+ MyMoneyMoney operator*( signed64 factor ) const;
+ MyMoneyMoney operator/( const MyMoneyMoney& Amount ) const;
+
+ // unary operators
+ MyMoneyMoney& operator+= ( const MyMoneyMoney& Amount );
+ MyMoneyMoney& operator-= ( const MyMoneyMoney& Amount );
+ MyMoneyMoney& operator/= ( const MyMoneyMoney& Amount );
+
+ // conversion
+ operator int() const;
+
+ static MyMoneyMoney maxValue;
+ static MyMoneyMoney minValue;
+ static MyMoneyMoney autoCalc;
+
+ bool isNegative() const { return (m_num < 0) ? true : false; }
+ bool isPositive() const { return (m_num > 0) ? true : false; }
+ bool isZero() const { return m_num == 0; }
+ bool isAutoCalc(void) const { return (*this == autoCalc); }
+
+ const MyMoneyMoney reduce(void) const;
+
+private:
+ signed64 m_num;
+ signed64 m_denom;
+
+ signed64 getLcd(const MyMoneyMoney& b) const;
+
+ KMYMONEY_EXPORT friend QDataStream &operator<<(QDataStream &, const MyMoneyMoney &);
+ KMYMONEY_EXPORT friend QDataStream &operator>>(QDataStream &, MyMoneyMoney &);
+
+ static unsigned char _thousandSeparator;
+ static unsigned char _decimalSeparator;
+ static signPosition _negativeMonetarySignPosition;
+ static signPosition _positiveMonetarySignPosition;
+ static bool _negativePrefixCurrencySymbol;
+ static bool _positivePrefixCurrencySymbol;
+ static MyMoneyMoney::fileVersionE _fileVersion;
+
+};
+
+//=============================================================================
+//
+// Inline functions
+//
+//=============================================================================
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: MyMoneyMoney
+// Purpose: Constructor - constructs object set to 0.
+// Returns: None
+// Throws: Nothing.
+// Arguments: None
+//
+////////////////////////////////////////////////////////////////////////////////
+inline MyMoneyMoney::MyMoneyMoney()
+{
+ m_num = 0;
+ m_denom = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: MyMoneyMoney
+// Purpose: Constructor - constructs object from an amount in a signed64 value
+// Returns: None
+// Throws: Nothing.
+// Arguments: Amount - signed 64 object containing amount
+// denom - denominator of the object
+//
+////////////////////////////////////////////////////////////////////////////////
+inline MyMoneyMoney::MyMoneyMoney(signed64 Amount, const signed64 denom)
+{
+ if(!denom)
+ throw new MYMONEYEXCEPTION("Denominator 0 not allowed!");
+
+ m_num = Amount;
+ m_denom = denom;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: MyMoneyMoney
+// Purpose: Constructor - constructs object from an amount in a double value
+// Returns: None
+// Throws: Nothing.
+// Arguments: dAmount - double object containing amount
+// denom - denominator of the object
+//
+////////////////////////////////////////////////////////////////////////////////
+inline MyMoneyMoney::MyMoneyMoney(const double dAmount, const signed64 denom)
+{
+ double adj = dAmount < 0 ? -0.5 : 0.5;
+ m_denom = denom;
+ m_num = (signed64) (dAmount * (double)m_denom + adj);
+}
+
+#if HAVE_LONG_DOUBLE
+////////////////////////////////////////////////////////////////////////////////
+// Name: MyMoneyMoney
+// Purpose: Constructor - constructs object from an amount in a long double value
+// Returns: None
+// Throws: Nothing.
+// Arguments: dAmount - long double object containing amount
+// denom - denominator of the object
+//
+////////////////////////////////////////////////////////////////////////////////
+inline MyMoneyMoney::MyMoneyMoney(const long double dAmount, const signed64 denom)
+{
+ long double adj = dAmount < 0 ? -0.5 : 0.5;
+ m_denom = denom;
+ m_num = static_cast<signed64> (dAmount * m_denom + adj);
+}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: MyMoneyMoney
+// Purpose: Constructor - constructs object from an amount in a integer value
+// Returns: None
+// Throws: Nothing.
+// Arguments: iAmount - integer object containing amount
+// denom - denominator of the object
+//
+////////////////////////////////////////////////////////////////////////////////
+inline MyMoneyMoney::MyMoneyMoney(const int iAmount, const signed64 denom)
+{
+ if(!denom)
+ throw new MYMONEYEXCEPTION("Denominator 0 not allowed!");
+
+ m_num = static_cast<signed64>(iAmount);
+ m_denom = denom;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: MyMoneyMoney
+// Purpose: Copy Constructor - constructs object from another MyMoneyMoney object
+// Returns: None
+// Throws: Nothing.
+// Arguments: Amount - MyMoneyMoney object to be copied
+//
+////////////////////////////////////////////////////////////////////////////////
+inline MyMoneyMoney::MyMoneyMoney(const MyMoneyMoney& Amount)
+{
+ m_num = Amount.m_num;
+ m_denom = Amount.m_denom;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: operator=
+// Purpose: Assignment operator - modifies object from input MyMoneyMoney object
+// Returns: Const reference to the object
+// Throws: Nothing.
+// Arguments: Amount - MyMoneyMoney object to be modified from
+//
+////////////////////////////////////////////////////////////////////////////////
+inline const MyMoneyMoney& MyMoneyMoney::operator=(const MyMoneyMoney& Amount)
+{
+ m_num = Amount.m_num;
+ m_denom = Amount.m_denom;
+ return *this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: operator=
+// Purpose: Assignment operator - modifies object from input NULL terminated
+// string
+// Returns: Const reference to the object
+// Throws: Nothing.
+// Arguments: pszAmount - NULL terminated string that contains amount
+//
+////////////////////////////////////////////////////////////////////////////////
+inline const MyMoneyMoney& MyMoneyMoney::operator=(const QString& pszAmount)
+{
+ *this = MyMoneyMoney( pszAmount );
+ return *this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: operator==
+// Purpose: Compare equal operator - compares object with input MyMoneyMoney object
+// Returns: true if equal, otherwise false
+// Throws: Nothing.
+// Arguments: Amount - MyMoneyMoney object to be compared with
+//
+////////////////////////////////////////////////////////////////////////////////
+inline bool MyMoneyMoney::operator==(const MyMoneyMoney& Amount) const
+{
+ if(m_denom == Amount.m_denom)
+ return m_num == Amount.m_num;
+
+ if(m_num == 0 && Amount.m_num == 0)
+ return true;
+
+ return (*this - Amount).m_num == 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: operator!=
+// Purpose: Compare not equal operator - compares object with input MyMoneyMoney object
+// Returns: true if not equal, otherwise false
+// Throws: Nothing.
+// Arguments: Amount - MyMoneyMoney object to be compared with
+//
+////////////////////////////////////////////////////////////////////////////////
+inline bool MyMoneyMoney::operator!=(const MyMoneyMoney& Amount) const
+{
+ if(m_num == Amount.m_num && m_denom == Amount.m_denom)
+ return false;
+
+ return (*this - Amount).m_num != 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: operator<
+// Purpose: Compare less than operator - compares object with input MyMoneyMoney object
+// Returns: true if object less than input amount
+// Throws: Nothing.
+// Arguments: Amount - MyMoneyMoney object to be compared with
+//
+////////////////////////////////////////////////////////////////////////////////
+inline bool MyMoneyMoney::operator<(const MyMoneyMoney& Amount) const
+{
+ if(m_denom == Amount.m_denom)
+ return (m_num < Amount.m_num);
+
+ signed64 ab, ba;
+
+ ab = m_num * Amount.m_denom;
+ ba = m_denom * Amount.m_num;
+
+ return ( ab < ba ) ;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: operator>
+// Purpose: Compare greater than operator - compares object with input MyMoneyMoney
+// object
+// Returns: true if object greater than input amount
+// Throws: Nothing.
+// Arguments: Amount - MyMoneyMoney object to be compared with
+//
+////////////////////////////////////////////////////////////////////////////////
+inline bool MyMoneyMoney::operator>(const MyMoneyMoney& Amount) const
+{
+ if(m_denom == Amount.m_denom)
+ return (m_num > Amount.m_num);
+
+ signed64 ab, ba;
+
+ ab = m_num * Amount.m_denom;
+ ba = m_denom * Amount.m_num;
+
+ return ( ab > ba ) ;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: operator<=
+// Purpose: Compare less than equal to operator - compares object with input
+// MyMoneyMoney object
+// Returns: true if object less than or equal to input amount
+// Throws: Nothing.
+// Arguments: Amount - MyMoneyMoney object to be compared with
+//
+////////////////////////////////////////////////////////////////////////////////
+inline bool MyMoneyMoney::operator<=(const MyMoneyMoney& Amount) const
+{
+ if(m_denom == Amount.m_denom)
+ return (m_num <= Amount.m_num);
+
+ signed64 ab, ba;
+
+ ab = m_num * Amount.m_denom;
+ ba = m_denom * Amount.m_num;
+
+ return ( ab <= ba ) ;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: operator>=
+// Purpose: Compare greater than equal to operator - compares object with input
+// MyMoneyMoney object
+// Returns: true if object greater than or equal to input amount
+// Throws: Nothing.
+// Arguments: Amount - MyMoneyMoney object to be compared with
+//
+////////////////////////////////////////////////////////////////////////////////
+inline bool MyMoneyMoney::operator>=(const MyMoneyMoney& Amount) const
+{
+ if(m_denom == Amount.m_denom)
+ return (m_num >= Amount.m_num);
+
+ signed64 ab, ba;
+
+ ab = m_num * Amount.m_denom;
+ ba = m_denom * Amount.m_num;
+
+ return ( ab >= ba ) ;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: operator==
+// Purpose: Compare equal operator - compares object with input amount in a
+// NULL terminated string
+// Returns: true if equal, otherwise false
+// Throws: Nothing.
+// Arguments: pszAmount - NULL terminated string that contains amount
+//
+////////////////////////////////////////////////////////////////////////////////
+inline bool MyMoneyMoney::operator==(const QString& pszAmount) const
+{
+ MyMoneyMoney Amount( pszAmount );
+ return ( *this == Amount ) ;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: operator!=
+// Purpose: Compare not equal operator - compares object with input amount in
+// a NULL terminated string
+// Returns: true if not equal, otherwise false
+// Throws: Nothing.
+// Arguments: pszAmount - NULL terminated string that contains amount
+//
+////////////////////////////////////////////////////////////////////////////////
+inline bool MyMoneyMoney::operator!=(const QString& pszAmount) const
+{
+ MyMoneyMoney Amount( pszAmount );
+ return ( *this != Amount ) ;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: operator-
+// Purpose: Unary operator - returns the negative value from the object
+// Returns: The current object
+// Throws: Nothing.
+// Arguments: None
+//
+////////////////////////////////////////////////////////////////////////////////
+inline MyMoneyMoney MyMoneyMoney::operator-() const
+{
+ MyMoneyMoney result(*this);
+ result.m_num = -result.m_num;
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: operator*
+// Purpose: Multiplication operator - multiplies the object with factor
+// Returns: The current object
+// Throws: Nothing.
+// Arguments: AmountInPence - signed64 object to be multiplied
+//
+////////////////////////////////////////////////////////////////////////////////
+inline MyMoneyMoney MyMoneyMoney::operator*(signed64 factor) const
+{
+ MyMoneyMoney result(*this);
+ result.m_num *= factor;
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: operator*
+// Purpose: Multiplication operator - multiplies the object with factor
+// Returns: The current object
+// Throws: Nothing.
+// Arguments: AmountInPence - long object to be multiplied
+//
+////////////////////////////////////////////////////////////////////////////////
+inline MyMoneyMoney MyMoneyMoney::operator*(int factor) const
+{
+ MyMoneyMoney result(*this);
+ result.m_num *= factor;
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: operator+=
+// Purpose: Addition operator - adds the input amount to the object together
+// Returns: Reference to the current object
+// Throws: Nothing.
+// Arguments: AmountInPence - MyMoneyMoney object to be added
+//
+////////////////////////////////////////////////////////////////////////////////
+inline MyMoneyMoney& MyMoneyMoney::operator+=(const MyMoneyMoney& AmountInPence)
+{
+ *this = *this + AmountInPence;
+ return *this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Name: operator-=
+// Purpose: Subtraction operator - subtracts the input amount from the object
+// Returns: Reference to the current object
+// Throws: Nothing.
+// Arguments: AmountInPence - MyMoneyMoney object to be subtracted
+//
+////////////////////////////////////////////////////////////////////////////////
+inline MyMoneyMoney& MyMoneyMoney::operator-=(const MyMoneyMoney& AmountInPence)
+{
+ *this = *this - AmountInPence;
+ return *this;
+}
+
+inline MyMoneyMoney& MyMoneyMoney::operator/=(const MyMoneyMoney& AmountInPence)
+{
+ *this = *this / AmountInPence;
+ return *this;
+}
+
+#endif
+
diff --git a/kmymoney2/mymoney/mymoneymoneytest.cpp b/kmymoney2/mymoney/mymoneymoneytest.cpp
new file mode 100644
index 0000000..64c2961
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneymoneytest.cpp
@@ -0,0 +1,591 @@
+/***************************************************************************
+ mymoneymoneytest.cpp
+ -------------------
+ copyright : (C) 2002 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+// make sure, that this is defined before we even include any other header file
+#ifndef __STDC_LIMIT_MACROS
+ #define __STDC_LIMIT_MACROS // force definition of min and max values
+#endif
+
+#include "mymoneymoneytest.h"
+#include "mymoneyexception.h"
+#include <iostream>
+#include <stdint.h>
+
+// make sure, we have the correct suffix
+#if SIZEOF_LONG == 8
+#define LLCONST(a) a ## L
+#else
+#define LLCONST(a) a ## LL
+#endif
+
+MyMoneyMoneyTest::MyMoneyMoneyTest()
+{
+}
+
+
+void MyMoneyMoneyTest::setUp()
+{
+ m_0 = new MyMoneyMoney(12);
+ m_1 = new MyMoneyMoney(-10);
+ m_2 = new MyMoneyMoney(2);
+ m_3 = new MyMoneyMoney(123,1);
+ m_4 = new MyMoneyMoney(1234,1000);
+ m_5 = new MyMoneyMoney(195883,100000);
+
+ MyMoneyMoney::setDecimalSeparator('.');
+ MyMoneyMoney::setThousandSeparator(',');
+ MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::BeforeQuantityMoney);
+}
+
+void MyMoneyMoneyTest::tearDown()
+{
+ delete m_0;
+ delete m_1;
+ delete m_2;
+ delete m_3;
+ delete m_4;
+ delete m_5;
+}
+
+void MyMoneyMoneyTest::testEmptyConstructor()
+{
+ MyMoneyMoney *m = new MyMoneyMoney();
+ CPPUNIT_ASSERT(m->m_num == 0);
+ CPPUNIT_ASSERT(m->m_denom == 1);
+ delete m;
+}
+
+void MyMoneyMoneyTest::testIntConstructor()
+{
+ CPPUNIT_ASSERT(m_0->m_num == 12);
+ CPPUNIT_ASSERT(m_0->m_denom == 100);
+
+ MyMoneyMoney a(123, 10000);
+ CPPUNIT_ASSERT(a.m_num == 123);
+ CPPUNIT_ASSERT(a.m_denom == 10000);
+}
+
+void MyMoneyMoneyTest::testAssignment()
+{
+ MyMoneyMoney *m = new MyMoneyMoney();
+ *m = *m_1;
+ CPPUNIT_ASSERT(m->m_num == -10);
+ CPPUNIT_ASSERT(m->m_denom == 100);
+#if 0
+ *m = 0;
+ CPPUNIT_ASSERT(m->m_num == 0);
+ CPPUNIT_ASSERT(m->m_denom == 100);
+
+ *m = 777888999;
+ CPPUNIT_ASSERT(m->m_num == 777888999);
+ CPPUNIT_ASSERT(m->m_denom == 100);
+
+ *m = (int)-5678;
+ CPPUNIT_ASSERT(m->m_num == -5678);
+ CPPUNIT_ASSERT(m->m_denom == 100);
+
+ *m = QString("-987");
+ CPPUNIT_ASSERT(m->m_num == -987);
+ CPPUNIT_ASSERT(m->m_denom == 1);
+
+ *m = QString("9998887776665554.44");
+ CPPUNIT_ASSERT(m->m_num == 999888777666555444LL);
+ CPPUNIT_ASSERT(m->m_denom == 100);
+
+ *m = QString("-99988877766655.444");
+ CPPUNIT_ASSERT(m->m_num == -99988877766655444LL);
+ CPPUNIT_ASSERT(m->m_denom == 1000);
+
+ *m = -666555444333222111LL;
+ CPPUNIT_ASSERT(m->m_num == -666555444333222111LL);
+ CPPUNIT_ASSERT(m->m_denom == 100);
+#endif
+ delete m;
+}
+
+void MyMoneyMoneyTest::testStringConstructor()
+{
+ MyMoneyMoney *m1 = new MyMoneyMoney("-999666555444");
+ CPPUNIT_ASSERT(m1->m_num == LLCONST(-999666555444));
+ CPPUNIT_ASSERT(m1->m_denom == 1);
+
+ MyMoneyMoney *m2 = new MyMoneyMoney("4445556669.99");
+ CPPUNIT_ASSERT(m2->m_num == LLCONST(444555666999));
+ CPPUNIT_ASSERT(m2->m_denom == 100);
+
+ delete m1;
+ delete m2;
+
+ m1 = new MyMoneyMoney("");
+ CPPUNIT_ASSERT(m1->m_num == LLCONST(0));
+ CPPUNIT_ASSERT(m1->m_denom == 1);
+ delete m1;
+
+ m1 = new MyMoneyMoney("1,123.");
+ CPPUNIT_ASSERT(m1->m_num == LLCONST(1123));
+ CPPUNIT_ASSERT(m1->m_denom == 1);
+ delete m1;
+
+ m1 = new MyMoneyMoney("123.1");
+ CPPUNIT_ASSERT(m1->m_num == LLCONST(1231));
+ CPPUNIT_ASSERT(m1->m_denom == 10);
+ delete m1;
+
+ m1 = new MyMoneyMoney("123.456");
+ CPPUNIT_ASSERT(m1->m_num == LLCONST(123456));
+ CPPUNIT_ASSERT(m1->m_denom == 1000);
+ delete m1;
+
+ m1 = new MyMoneyMoney("12345/100");
+ CPPUNIT_ASSERT(m1->m_num == LLCONST(12345));
+ CPPUNIT_ASSERT(m1->m_denom == 100);
+ delete m1;
+
+ MyMoneyMoney::setDecimalSeparator(',');
+ MyMoneyMoney::setThousandSeparator('.');
+ MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::ParensAround);
+ m1 = new MyMoneyMoney("x1.234,567 EUR");
+ CPPUNIT_ASSERT(m1->m_num == LLCONST(1234567));
+ CPPUNIT_ASSERT(m1->m_denom == 1000);
+ delete m1;
+
+ m1 = new MyMoneyMoney("x(1.234,567) EUR");
+ CPPUNIT_ASSERT(m1->m_num == LLCONST(-1234567));
+ CPPUNIT_ASSERT(m1->m_denom == 1000);
+ delete m1;
+
+ m1 = new MyMoneyMoney("1 5/8");
+ CPPUNIT_ASSERT(m1->m_num == LLCONST(13));
+ CPPUNIT_ASSERT(m1->m_denom == 8);
+ delete m1;
+}
+
+void MyMoneyMoneyTest::testConvert()
+{
+ MyMoneyMoney a("123.456");
+ MyMoneyMoney b = a.convert(100);
+ CPPUNIT_ASSERT(b.m_num == 12346);
+ CPPUNIT_ASSERT(b.m_denom == 100);
+ a = QString("-123.456");
+ b = a.convert(100);
+ CPPUNIT_ASSERT(b.m_num == -12346);
+ CPPUNIT_ASSERT(b.m_denom == 100);
+
+ a = QString("123.1");
+ b = a.convert(100);
+ CPPUNIT_ASSERT(b.m_num == 12310);
+ CPPUNIT_ASSERT(b.m_denom == 100);
+
+ a = QString("-73010.28");
+ b = QString("1.95583");
+ CPPUNIT_ASSERT((a * b).convert(100) == QString("-142795.70"));
+
+ a = QString("-142795.69");
+ CPPUNIT_ASSERT((a / b).convert(100) == QString("-73010.28"));
+}
+
+void MyMoneyMoneyTest::testEquality()
+{
+ CPPUNIT_ASSERT (*m_1 == *m_1);
+ CPPUNIT_ASSERT (!(*m_1 == *m_0));
+
+ MyMoneyMoney m1(LLCONST(999666555444));
+ MyMoneyMoney m2(LLCONST(999666555444));
+ CPPUNIT_ASSERT(m1 == m2);
+
+ MyMoneyMoney m3(LLCONST(-999666555444));
+ MyMoneyMoney m4(LLCONST(-999666555444));
+ CPPUNIT_ASSERT(m3 == m4);
+
+ MyMoneyMoney m5(1230,100);
+ MyMoneyMoney m6(123,10);
+ MyMoneyMoney m7(246,20);
+ CPPUNIT_ASSERT(m5 == m6);
+ CPPUNIT_ASSERT(m5 == m7);
+
+ CPPUNIT_ASSERT(m5 == QString("369/30"));
+
+ CPPUNIT_ASSERT(MyMoneyMoney::autoCalc == MyMoneyMoney::autoCalc);
+}
+
+void MyMoneyMoneyTest::testInequality()
+{
+ CPPUNIT_ASSERT (*m_1 != *m_0);
+ CPPUNIT_ASSERT (!(*m_1 != *m_1));
+
+ MyMoneyMoney m1(LLCONST(999666555444));
+ MyMoneyMoney m2(LLCONST(-999666555444));
+ CPPUNIT_ASSERT(m1 != m2);
+
+ MyMoneyMoney m3(LLCONST(-999666555444));
+ MyMoneyMoney m4(LLCONST(999666555444));
+ CPPUNIT_ASSERT(m3 != m4);
+
+ CPPUNIT_ASSERT(m4 != QString("999666555444"));
+
+ CPPUNIT_ASSERT(MyMoneyMoney::autoCalc != MyMoneyMoney(1,100));
+ CPPUNIT_ASSERT(MyMoneyMoney(1,100) != MyMoneyMoney::autoCalc);
+}
+
+
+void MyMoneyMoneyTest::testAddition()
+{
+ CPPUNIT_ASSERT (*m_0 + *m_1 == *m_2);
+
+ MyMoneyMoney m1(100);
+
+ // CPPUNIT_ASSERT((m1 + 50) == MyMoneyMoney(51,1));
+ // CPPUNIT_ASSERT((m1 + 1000000000) == MyMoneyMoney(1000000001,1));
+ // CPPUNIT_ASSERT((m1 + -50) == MyMoneyMoney(-49,1));
+
+ CPPUNIT_ASSERT((m1 += *m_0) == MyMoneyMoney(112));
+ // CPPUNIT_ASSERT((m1 += -12) == MyMoneyMoney(100));
+
+ // m1++;
+ // CPPUNIT_ASSERT(m1 == MyMoneyMoney(101));
+ // CPPUNIT_ASSERT((++m1) == MyMoneyMoney(102));
+
+ m1 = QString("123.20");
+ MyMoneyMoney m2(40, 1000);
+ CPPUNIT_ASSERT((m1 + m2) == QString("123.24"));
+
+ m1 += m2;
+ CPPUNIT_ASSERT(m1.m_num == 123240);
+ CPPUNIT_ASSERT(m1.m_denom == 1000);
+}
+
+void MyMoneyMoneyTest::testSubtraction()
+{
+ CPPUNIT_ASSERT (*m_2 - *m_1 == *m_0);
+
+ MyMoneyMoney m1(100);
+
+ // CPPUNIT_ASSERT((m1-50) == MyMoneyMoney(-49,1));
+ // CPPUNIT_ASSERT((m1-1000000000) == MyMoneyMoney(-999999999,1));
+ // CPPUNIT_ASSERT((m1 - -50) == MyMoneyMoney(51,1));
+
+ CPPUNIT_ASSERT((m1 -= *m_0) == MyMoneyMoney(88));
+ // CPPUNIT_ASSERT((m1 -= -12) == MyMoneyMoney(100));
+
+ // m1--;
+ // CPPUNIT_ASSERT(m1 == MyMoneyMoney(99));
+ // CPPUNIT_ASSERT((--m1) == MyMoneyMoney(98));
+
+ m1 = QString("123.20");
+ MyMoneyMoney m2(1, 5);
+ CPPUNIT_ASSERT((m1 - m2) == MyMoneyMoney(123,1));
+
+ m1 -= m2;
+ CPPUNIT_ASSERT(m1.m_num == 12300);
+ CPPUNIT_ASSERT(m1.m_denom == 100);
+}
+
+void MyMoneyMoneyTest::testMultiplication()
+{
+ MyMoneyMoney m1(100,1);
+
+ CPPUNIT_ASSERT((m1 * MyMoneyMoney(50,1)) == MyMoneyMoney(5000,1));
+ CPPUNIT_ASSERT((m1 * MyMoneyMoney(10000000,1)) == MyMoneyMoney(1000000000,1));
+ CPPUNIT_ASSERT((m1 * (*m_0)) == MyMoneyMoney(1200));
+
+ MyMoneyMoney m2 = QString("-73010.28");
+ m1 = QString("1.95583");
+ CPPUNIT_ASSERT((m1 * m2) == QString("-142795.6959324"));
+}
+
+void MyMoneyMoneyTest::testDivision()
+{
+ MyMoneyMoney m1(100);
+ CPPUNIT_ASSERT((m1 / MyMoneyMoney(50)) == MyMoneyMoney(2,1));
+
+ MyMoneyMoney m2 = QString("-142795.69");
+ m1 = QString("1.95583");
+ CPPUNIT_ASSERT((m2 / m1).convert(100000000) == QString("-73010.27696681"));
+
+ MyMoneyMoney m3 = MyMoneyMoney(0) / MyMoneyMoney(100);
+ CPPUNIT_ASSERT(m3.m_num == 0);
+ CPPUNIT_ASSERT(m3.m_denom != 0);
+}
+
+void MyMoneyMoneyTest::testSetDecimalSeparator()
+{
+ MyMoneyMoney m1(100000);
+ MyMoneyMoney m2(200000);
+
+ CPPUNIT_ASSERT(m1.formatMoney("", 2) == QString("1,000.00"));
+ CPPUNIT_ASSERT(MyMoneyMoney::decimalSeparator() == '.');
+
+ MyMoneyMoney::setDecimalSeparator(':');
+ CPPUNIT_ASSERT(m1.formatMoney("", 2) == QString("1,000:00"));
+ CPPUNIT_ASSERT(m2.formatMoney("", 2) == QString("2,000:00"));
+
+ CPPUNIT_ASSERT(MyMoneyMoney::decimalSeparator() == ':');
+}
+
+void MyMoneyMoneyTest::testSetThousandSeparator()
+{
+ MyMoneyMoney m1(100000);
+ MyMoneyMoney m2(200000);
+
+ CPPUNIT_ASSERT(m1.formatMoney("", 2) == QString("1,000.00"));
+ CPPUNIT_ASSERT(MyMoneyMoney::thousandSeparator() == ',');
+
+ MyMoneyMoney::setThousandSeparator(':');
+ CPPUNIT_ASSERT(m1.formatMoney("", 2) == QString("1:000.00"));
+ CPPUNIT_ASSERT(m2.formatMoney("", 2) == QString("2:000.00"));
+
+ CPPUNIT_ASSERT(MyMoneyMoney::thousandSeparator() == ':');
+}
+
+void MyMoneyMoneyTest::testFormatMoney()
+{
+ CPPUNIT_ASSERT(m_0->formatMoney("", 2) == QString("0.12"));
+ CPPUNIT_ASSERT(m_1->formatMoney("", 2) == QString("-0.10"));
+
+ MyMoneyMoney m1(10099);
+ CPPUNIT_ASSERT(m1.formatMoney("", 2) == QString("100.99"));
+
+ m1 = MyMoneyMoney(100,1);
+ CPPUNIT_ASSERT(m1.formatMoney("", 2) == QString("100.00"));
+ CPPUNIT_ASSERT(m1.formatMoney("", -1) == QString("100"));
+
+ m1 = m1 * 10;
+ CPPUNIT_ASSERT(m1.formatMoney("", 2) == QString("1,000.00"));
+ CPPUNIT_ASSERT(m1.formatMoney("", -1) == QString("1,000"));
+ CPPUNIT_ASSERT(m1.formatMoney("", -1, false) == QString("1000"));
+ CPPUNIT_ASSERT(m1.formatMoney("", 3, false) == QString("1000.000"));
+
+ m1 = MyMoneyMoney(INT64_MAX, 100);
+ CPPUNIT_ASSERT(m1.formatMoney("", 2) == QString("92,233,720,368,547,758.07"));
+ CPPUNIT_ASSERT(m1.formatMoney(100) == QString("92,233,720,368,547,758.07"));
+ CPPUNIT_ASSERT(m1.formatMoney("", 2, false) == QString("92233720368547758.07"));
+ CPPUNIT_ASSERT(m1.formatMoney(100, false) == QString("92233720368547758.07"));
+
+ m1 = MyMoneyMoney(INT64_MIN, 100);
+ CPPUNIT_ASSERT(m1.formatMoney("", 2) == QString("-92,233,720,368,547,758.08"));
+ CPPUNIT_ASSERT(m1.formatMoney(100) == QString("-92,233,720,368,547,758.08"));
+ CPPUNIT_ASSERT(m1.formatMoney("", 2, false) == QString("-92233720368547758.08"));
+ CPPUNIT_ASSERT(m1.formatMoney(100, false) == QString("-92233720368547758.08"));
+
+ m1 = MyMoneyMoney(1,5);
+ CPPUNIT_ASSERT(m1.formatMoney("", 2) == QString("0.20"));
+ CPPUNIT_ASSERT(m1.formatMoney(1000) == QString("0.200"));
+ CPPUNIT_ASSERT(m1.formatMoney(100) == QString("0.20"));
+ CPPUNIT_ASSERT(m1.formatMoney(10) == QString("0.2"));
+
+ m1 = MyMoneyMoney(13333,5000);
+ CPPUNIT_ASSERT(m1.formatMoney("", 10) == QString("2.6666000000"));
+
+ m1 = MyMoneyMoney(-1404,100);
+ CPPUNIT_ASSERT(m1.formatMoney("",-1) == QString("-14.04"));
+}
+
+void MyMoneyMoneyTest::testRelation()
+{
+ MyMoneyMoney m1(100);
+ MyMoneyMoney m2(50);
+ MyMoneyMoney m3(100);
+
+ // tests with same denominator
+ CPPUNIT_ASSERT(m1 > m2);
+ CPPUNIT_ASSERT(m2 < m1);
+
+ CPPUNIT_ASSERT(m1 <= m3);
+ CPPUNIT_ASSERT(m3 >= m1);
+ CPPUNIT_ASSERT(m1 <= m1);
+ CPPUNIT_ASSERT(m3 >= m3);
+
+ // tests with different denominator
+ m1 = QString("1/8");
+ m2 = QString("1/7");
+ CPPUNIT_ASSERT(m1 < m2);
+ CPPUNIT_ASSERT(m2 > m1);
+ m2 = QString("-1/7");
+ CPPUNIT_ASSERT(m2 < m1);
+ CPPUNIT_ASSERT(m1 > m2);
+ CPPUNIT_ASSERT(m1 >= m2);
+ CPPUNIT_ASSERT(m2 <= m1);
+
+ m1 = QString("-2/14");
+ CPPUNIT_ASSERT(m1 >= m2);
+ CPPUNIT_ASSERT(m1 <= m2);
+
+}
+
+void MyMoneyMoneyTest::testUnaryMinus()
+{
+ MyMoneyMoney m1(100);
+ MyMoneyMoney m2;
+
+ m2 = -m1;
+
+ CPPUNIT_ASSERT(m1 == MyMoneyMoney(100));
+ CPPUNIT_ASSERT(m2 == MyMoneyMoney(-100));
+}
+
+void MyMoneyMoneyTest::testDoubleConstructor()
+{
+ for(int i = -123456; i < 123456; ++i) {
+ double d = i;
+ MyMoneyMoney r(i);
+ d /= 100;
+ MyMoneyMoney t(d);
+ CPPUNIT_ASSERT(t == r);
+ }
+}
+
+void MyMoneyMoneyTest::testAbsoluteFunction()
+{
+ MyMoneyMoney m1(-100);
+ MyMoneyMoney m2(100);
+
+ CPPUNIT_ASSERT(m2.abs() == MyMoneyMoney(100));
+ CPPUNIT_ASSERT(m1.abs() == MyMoneyMoney(100));
+}
+
+void MyMoneyMoneyTest::testToString()
+{
+ MyMoneyMoney m1(-100);
+ MyMoneyMoney m2(1234);
+ MyMoneyMoney m3;
+
+ CPPUNIT_ASSERT(m1.toString() == QString("-100/100"));
+ CPPUNIT_ASSERT(m2.toString() == QString("1234/100"));
+ CPPUNIT_ASSERT(m3.toString() == QString("0/1"));
+}
+
+void MyMoneyMoneyTest::testNegativeSignPos(void)
+{
+ MyMoneyMoney m("-123456/100");
+
+ MyMoneyMoney::signPosition pos = MyMoneyMoney::negativeMonetarySignPosition();
+
+ MyMoneyMoney::setNegativePrefixCurrencySymbol(false);
+ MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::ParensAround);
+ CPPUNIT_ASSERT(m.formatMoney("CUR", 2) == "(1,234.56) CUR");
+ MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::BeforeQuantityMoney);
+ CPPUNIT_ASSERT(m.formatMoney("CUR", 2) == "-1,234.56 CUR");
+ MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::AfterQuantityMoney);
+ CPPUNIT_ASSERT(m.formatMoney("CUR", 2) == "1,234.56- CUR");
+ MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::BeforeMoney);
+ CPPUNIT_ASSERT(m.formatMoney("CUR", 2) == "1,234.56 -CUR");
+ MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::AfterMoney);
+ CPPUNIT_ASSERT(m.formatMoney("CUR", 2) == "1,234.56 CUR-");
+
+ MyMoneyMoney::setNegativePrefixCurrencySymbol(true);
+ MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::ParensAround);
+ CPPUNIT_ASSERT(m.formatMoney("CUR", 2) == "CUR (1,234.56)");
+ MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::BeforeQuantityMoney);
+ CPPUNIT_ASSERT(m.formatMoney("CUR", 2) == "CUR -1,234.56");
+ MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::AfterQuantityMoney);
+ CPPUNIT_ASSERT(m.formatMoney("CUR", 2) == "CUR 1,234.56-");
+ MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::BeforeMoney);
+ CPPUNIT_ASSERT(m.formatMoney("CUR", 2) == "-CUR 1,234.56");
+ MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::AfterMoney);
+ CPPUNIT_ASSERT(m.formatMoney("CUR", 2) == "CUR- 1,234.56");
+
+ MyMoneyMoney::setNegativeMonetarySignPosition(pos);
+}
+
+void MyMoneyMoneyTest::testPositiveSignPos(void)
+{
+ MyMoneyMoney m("123456/100");
+
+ MyMoneyMoney::signPosition pos = MyMoneyMoney::positiveMonetarySignPosition();
+
+ MyMoneyMoney::setPositivePrefixCurrencySymbol(false);
+ MyMoneyMoney::setPositiveMonetarySignPosition(MyMoneyMoney::ParensAround);
+ CPPUNIT_ASSERT(m.formatMoney("CUR", 2) == "(1,234.56) CUR");
+ MyMoneyMoney::setPositiveMonetarySignPosition(MyMoneyMoney::BeforeQuantityMoney);
+ CPPUNIT_ASSERT(m.formatMoney("CUR", 2) == "1,234.56 CUR");
+ MyMoneyMoney::setPositiveMonetarySignPosition(MyMoneyMoney::AfterQuantityMoney);
+ CPPUNIT_ASSERT(m.formatMoney("CUR", 2) == "1,234.56 CUR");
+ MyMoneyMoney::setPositiveMonetarySignPosition(MyMoneyMoney::BeforeMoney);
+ CPPUNIT_ASSERT(m.formatMoney("CUR", 2) == "1,234.56 CUR");
+ MyMoneyMoney::setPositiveMonetarySignPosition(MyMoneyMoney::AfterMoney);
+ CPPUNIT_ASSERT(m.formatMoney("CUR", 2) == "1,234.56 CUR");
+
+ MyMoneyMoney::setPositivePrefixCurrencySymbol(true);
+ MyMoneyMoney::setPositiveMonetarySignPosition(MyMoneyMoney::ParensAround);
+ CPPUNIT_ASSERT(m.formatMoney("CUR", 2) == "CUR (1,234.56)");
+ MyMoneyMoney::setPositiveMonetarySignPosition(MyMoneyMoney::BeforeQuantityMoney);
+ CPPUNIT_ASSERT(m.formatMoney("CUR", 2) == "CUR 1,234.56");
+ MyMoneyMoney::setPositiveMonetarySignPosition(MyMoneyMoney::AfterQuantityMoney);
+ CPPUNIT_ASSERT(m.formatMoney("CUR", 2) == "CUR 1,234.56");
+ MyMoneyMoney::setPositiveMonetarySignPosition(MyMoneyMoney::BeforeMoney);
+ CPPUNIT_ASSERT(m.formatMoney("CUR", 2) == "CUR 1,234.56");
+ MyMoneyMoney::setPositiveMonetarySignPosition(MyMoneyMoney::AfterMoney);
+ CPPUNIT_ASSERT(m.formatMoney("CUR", 2) == "CUR 1,234.56");
+
+ MyMoneyMoney::setPositiveMonetarySignPosition(pos);
+}
+
+void MyMoneyMoneyTest::testNegativeStringConstructor(void)
+{
+ MyMoneyMoney *m1;
+ MyMoneyMoney::setDecimalSeparator(',');
+ MyMoneyMoney::setThousandSeparator('.');
+ MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::ParensAround);
+ m1 = new MyMoneyMoney("x(1.234,567) EUR");
+ CPPUNIT_ASSERT(m1->m_num == LLCONST(-1234567));
+ CPPUNIT_ASSERT(m1->m_denom == 1000);
+ delete m1;
+ MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::BeforeQuantityMoney);
+ m1 = new MyMoneyMoney("x1.234,567- EUR");
+ CPPUNIT_ASSERT(m1->m_num == LLCONST(-1234567));
+ CPPUNIT_ASSERT(m1->m_denom == 1000);
+ delete m1;
+ m1 = new MyMoneyMoney("x1.234,567 -EUR");
+ CPPUNIT_ASSERT(m1->m_num == LLCONST(-1234567));
+ CPPUNIT_ASSERT(m1->m_denom == 1000);
+ delete m1;
+ m1 = new MyMoneyMoney("-1.234,567 EUR");
+ CPPUNIT_ASSERT(m1->m_num == LLCONST(-1234567));
+ CPPUNIT_ASSERT(m1->m_denom == 1000);
+ delete m1;
+}
+
+void MyMoneyMoneyTest::testReduce(void)
+{
+ MyMoneyMoney a(36488100, 1267390000);
+ MyMoneyMoney b(-a);
+
+ a = a.reduce();
+ CPPUNIT_ASSERT(a.m_num == 364881);
+ CPPUNIT_ASSERT(a.m_denom == 12673900);
+
+ b = b.reduce();
+ CPPUNIT_ASSERT(b.m_num == -364881);
+ CPPUNIT_ASSERT(b.m_denom == 12673900);
+}
+
+void MyMoneyMoneyTest::testZeroDenominator()
+{
+ try {
+ MyMoneyMoney m((int)1, 0);
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+
+ try {
+ MyMoneyMoney m((signed64)1, 0);
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+}
+
diff --git a/kmymoney2/mymoney/mymoneymoneytest.h b/kmymoney2/mymoney/mymoneymoneytest.h
new file mode 100644
index 0000000..ffb2c0e
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneymoneytest.h
@@ -0,0 +1,99 @@
+/***************************************************************************
+ mymoneymoneytest.h
+ -------------------
+ copyright : (C) 2002 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 __MYMONEYMONEYTEST_H__
+#define __MYMONEYMONEYTEST_H__
+
+// Check for standard definitions
+#ifdef HAVE_STDINT_H
+ #ifndef __STDC_LIMIT_MACROS
+ #define __STDC_LIMIT_MACROS // force definition of min and max values
+ #endif
+ #include <stdint.h>
+#else
+ #include <limits.h>
+ #define INT64_MAX LLONG_MAX
+ #define INT64_MIN LLONG_MIN
+#endif
+
+#include <cppunit/extensions/HelperMacros.h>
+
+#define private public
+#include "mymoneymoney.h"
+#undef private
+
+class MyMoneyMoneyTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(MyMoneyMoneyTest);
+ CPPUNIT_TEST(testEmptyConstructor);
+ CPPUNIT_TEST(testIntConstructor);
+ CPPUNIT_TEST(testStringConstructor);
+ CPPUNIT_TEST(testAssignment);
+ CPPUNIT_TEST(testConvert);
+ CPPUNIT_TEST(testEquality);
+ CPPUNIT_TEST(testInequality);
+ CPPUNIT_TEST(testAddition);
+ CPPUNIT_TEST(testSubtraction);
+ CPPUNIT_TEST(testMultiplication);
+ CPPUNIT_TEST(testDivision);
+ CPPUNIT_TEST(testSetDecimalSeparator);
+ CPPUNIT_TEST(testSetThousandSeparator);
+ CPPUNIT_TEST(testFormatMoney);
+ CPPUNIT_TEST(testRelation);
+ CPPUNIT_TEST(testUnaryMinus);
+ CPPUNIT_TEST(testDoubleConstructor);
+ CPPUNIT_TEST(testAbsoluteFunction);
+ CPPUNIT_TEST(testToString);
+ CPPUNIT_TEST(testNegativeSignPos);
+ CPPUNIT_TEST(testPositiveSignPos);
+ CPPUNIT_TEST(testNegativeStringConstructor);
+ CPPUNIT_TEST(testReduce);
+ CPPUNIT_TEST(testZeroDenominator);
+ CPPUNIT_TEST_SUITE_END();
+
+protected:
+ MyMoneyMoney *m_0, *m_1, *m_2, *m_3, *m_4, *m_5;
+public:
+ MyMoneyMoneyTest();
+
+ void setUp();
+ void tearDown();
+ void testEmptyConstructor();
+ void testIntConstructor();
+ void testStringConstructor();
+ void testAssignment();
+ void testConvert();
+ void testEquality();
+ void testInequality();
+ void testAddition();
+ void testSubtraction();
+ void testMultiplication();
+ void testDivision();
+ void testFormatMoney();
+ void testSetDecimalSeparator();
+ void testSetThousandSeparator();
+ void testRelation();
+ void testUnaryMinus();
+ void testDoubleConstructor();
+ void testAbsoluteFunction();
+ void testToString();
+ void testNegativeSignPos();
+ void testPositiveSignPos();
+ void testNegativeStringConstructor();
+ void testReduce();
+ void testZeroDenominator();
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneyobject.cpp b/kmymoney2/mymoney/mymoneyobject.cpp
new file mode 100644
index 0000000..7939ff0
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyobject.cpp
@@ -0,0 +1,74 @@
+/***************************************************************************
+ mymoneyobject.cpp
+ -------------------
+ copyright : (C) 2005 by Thomas Baumagrt
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "mymoneyobject.h"
+#include "mymoneyutils.h"
+#include "mymoneyexception.h"
+
+const QString MyMoneyObject::m_emptyId;
+
+MyMoneyObject::MyMoneyObject(const QString& id)
+{
+ m_id = id;
+}
+
+MyMoneyObject::MyMoneyObject(const QDomElement& el, const bool forceId)
+{
+ m_id = QStringEmpty(el.attribute("id"));
+ if(m_id.length() == 0 && forceId)
+ throw new MYMONEYEXCEPTION("Node has no ID");
+}
+
+MyMoneyObject::MyMoneyObject()
+{
+}
+
+MyMoneyObject::~MyMoneyObject()
+{
+}
+
+void MyMoneyObject::setId(const QString& id)
+{
+ m_id = id;
+}
+
+bool MyMoneyObject::operator == (const MyMoneyObject& right) const
+{
+ return m_id == right.m_id;
+}
+
+void MyMoneyObject::clearId(void)
+{
+ m_id = QString();
+}
+
+const QString& MyMoneyObject::emptyId(void)
+{
+ return m_emptyId;
+}
+
+void MyMoneyObject::writeBaseXML(QDomDocument& document, QDomElement& el) const
+{
+ Q_UNUSED(document);
+
+ el.setAttribute("id", m_id);
+}
diff --git a/kmymoney2/mymoney/mymoneyobject.h b/kmymoney2/mymoney/mymoneyobject.h
new file mode 100644
index 0000000..d07296b
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyobject.h
@@ -0,0 +1,129 @@
+/***************************************************************************
+ mymoneyobject.h
+ -------------------
+ copyright : (C) 2005 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYOBJECT_H
+#define MYMONEYOBJECT_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstring.h>
+#include <qdom.h>
+#include <qdatetime.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include <kmymoney/export.h>
+
+/**
+ * @author Thomas Baumgart
+ */
+
+/**
+ * This class represents the base class of all MyMoney objects.
+ */
+class KMYMONEY_EXPORT MyMoneyObject
+{
+public:
+ /**
+ * This is the constructor for the MyMoneyObject object
+ */
+ MyMoneyObject();
+
+ /**
+ * This is the destructor for the MyMoneyObject object
+ */
+ virtual ~MyMoneyObject();
+
+ /**
+ * This method retrieves the id of the object
+ *
+ * @return ID of object
+ */
+ const QString& id(void) const { return m_id; };
+
+ /**
+ * This method clears the id of the object
+ */
+ void clearId(void);
+
+ /**
+ * This method must be provided by all derived objects. It returns,
+ * a @p true if the object is referencing the one requested by the
+ * parameter @p id. If it does not, this method returns @p false.
+ *
+ * @param id id of the object to be checked for references
+ * @retval true This object references object with id @p id.
+ * @retval false This object does not reference the object with id @p id.
+ */
+ virtual bool hasReferenceTo(const QString& id) const = 0;
+
+ /**
+ * This method creates a QDomElement for the @p document
+ * under the parent node @p parent.
+ *
+ * @param document reference to QDomDocument
+ * @param parent reference to QDomElement parent node
+ */
+ virtual void writeXML(QDomDocument& document, QDomElement& parent) const = 0;
+
+ bool operator == (const MyMoneyObject& right) const;
+
+ static const QString& emptyId(void);
+
+protected:
+ /**
+ * This contructor assigns the id to the MyMoneyObject
+ *
+ * @param id ID of object
+ */
+ MyMoneyObject(const QString& id);
+
+ /**
+ * This contructor reads the id from the @p id attribute of the
+ * QDomElement.
+ *
+ * @param node const reference to the QDomElement from which to
+ * obtain the id of the object
+ * @param forceId flag to be able to suppress enforcement of an id
+ * defaults to true which requires the node to have an
+ * attribute with name @p id. If it does not contain such
+ * an attribute, an exception will be thrown. If @p forceId
+ * is false, no check for an id is performed. This will be
+ * used by objects, which are stored w/o id (eg. splits,
+ * transactions within schedules)
+ */
+ MyMoneyObject(const QDomElement& node, const bool forceId = true);
+
+ void setId(const QString& id) /* __attribute__ ((deprecated)) */;
+
+ /**
+ * This method writes out the members contained in this object.
+ */
+ void writeBaseXML(QDomDocument& document, QDomElement& el) const;
+
+protected:
+ QString m_id;
+ static const QString m_emptyId;
+};
+
+#endif
+
diff --git a/kmymoney2/mymoney/mymoneyobjectcontainer.cpp b/kmymoney2/mymoney/mymoneyobjectcontainer.cpp
new file mode 100644
index 0000000..666da3b
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyobjectcontainer.cpp
@@ -0,0 +1,218 @@
+/***************************************************************************
+ mymoneyobjectcontainer.cpp
+ -------------------
+ copyright : (C) 2006 by Thomas Baumagrt
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include <kmymoney/mymoneyobjectcontainer.h>
+
+MyMoneyObjectContainer::MyMoneyObjectContainer()
+{
+}
+
+MyMoneyObjectContainer::~MyMoneyObjectContainer()
+{
+ clear();
+}
+
+void MyMoneyObjectContainer::clear(IMyMoneyStorage* storage)
+{
+ // delete all objects
+ QMap<QString, MyMoneyObject const *>::const_iterator it;
+ for(it = m_map.begin(); it != m_map.end(); ++it) {
+ delete (*it);
+ }
+
+ // then delete the pointers to them
+ m_map.clear();
+
+ if(storage)
+ m_storage = storage;
+}
+
+void MyMoneyObjectContainer::clear(const QString& id)
+{
+ QMap<QString, MyMoneyObject const *>::iterator it;
+ it = m_map.find(id);
+ if(it != m_map.end()) {
+ delete (*it);
+ m_map.erase(it);
+ }
+}
+
+#define listMethod(a, T) \
+void MyMoneyObjectContainer::a(QValueList<T>& list) \
+{ \
+ QMap<QString, const MyMoneyObject*>::const_iterator it; \
+ for(it = m_map.begin(); it != m_map.end(); ++it) { \
+ const T* node = dynamic_cast<const T*>(*it); \
+ if(node) { \
+ list.append(*node); \
+ } \
+ } \
+}
+
+#define preloadListMethod(a, T) \
+void MyMoneyObjectContainer::preload##a(const QValueList<T>& list) \
+{ \
+ QValueList<T>::const_iterator it; \
+ for(it = list.begin(); it != list.end(); ++it) { \
+ delete m_map[(*it).id()]; \
+ m_map[(*it).id()] = new T(*it); \
+ } \
+}
+
+#define preloadMethod(a, T) \
+void MyMoneyObjectContainer::preload##a(const T& obj) \
+{ \
+ delete m_map[obj.id()]; \
+ m_map[obj.id()] = new T(obj); \
+}
+
+#define objectAccessMethod(a, T) \
+const T& MyMoneyObjectContainer::a(const QString& id) \
+{ \
+ static T nullElement; \
+ if(id.isEmpty()) \
+ return nullElement; \
+ QMap<QString, MyMoneyObject const *>::const_iterator it; \
+ it = m_map.find(id); \
+ if(it == m_map.end()) { \
+ /* not found, need to load from engine */ \
+ T x = m_storage->a(id); \
+ m_map[id] = new T(x); \
+ return dynamic_cast<const T&>(*m_map[id]); \
+ } \
+ return dynamic_cast<const T&>(*(*it)); \
+}
+
+void MyMoneyObjectContainer::account(QValueList<MyMoneyAccount>& list)
+{
+ QMap<QString, const MyMoneyObject*>::const_iterator it;
+ for(it = m_map.begin(); it != m_map.end(); ++it) {
+ const MyMoneyAccount* node = dynamic_cast<const MyMoneyAccount*>(*it);
+ if(node) {
+ assignFraction(const_cast<MyMoneyAccount*>(node));
+ list.append(*node);
+ }
+ }
+}
+
+const MyMoneyAccount& MyMoneyObjectContainer::account(const QString& id)
+{
+ static MyMoneyAccount nullElement;
+ if(id.isEmpty())
+ return nullElement;
+ QMap<QString, MyMoneyObject const *>::iterator it;
+ it = m_map.find(id);
+ if(it == m_map.end()) {
+ /* not found, need to load from engine */
+ MyMoneyAccount x = m_storage->account(id);
+ MyMoneyAccount* item = new MyMoneyAccount(x);
+ assignFraction(dynamic_cast<MyMoneyAccount*>(item));
+ m_map[id] = item;
+ return dynamic_cast<const MyMoneyAccount&>(*m_map[id]);
+ }
+ assignFraction(dynamic_cast<MyMoneyAccount*> (const_cast<MyMoneyObject*>(*it)));
+ return dynamic_cast<const MyMoneyAccount&>(*(*it));
+}
+
+void MyMoneyObjectContainer::assignFraction(MyMoneyAccount* acc)
+{
+ if(acc != 0 && acc->m_fraction == -1) {
+ const MyMoneySecurity& sec = security(acc->currencyId());
+ acc->fraction(sec);
+ }
+}
+
+const MyMoneyAccount& MyMoneyObjectContainer::accountByName(const QString& name) const
+{
+ static MyMoneyAccount nullElement;
+ QMap<QString, MyMoneyObject const *>::const_iterator it;
+ for(it = m_map.begin(); it != m_map.end(); ++it) {
+ const MyMoneyAccount* node = dynamic_cast<const MyMoneyAccount *>(*it);
+ if(node) {
+ if(node->name() == name)
+ return dynamic_cast<const MyMoneyAccount &>(*(*it));
+ }
+ }
+ return nullElement;
+}
+
+void MyMoneyObjectContainer::refresh(const QString& id)
+{
+ if(id.isEmpty())
+ return;
+
+ QMap<QString, MyMoneyObject const *>::const_iterator it;
+ it = m_map.find(id);
+ if(it != m_map.end()) {
+ const MyMoneyAccount* account = dynamic_cast<const MyMoneyAccount *>(*it);
+ const MyMoneyPayee* payee = dynamic_cast<const MyMoneyPayee *>(*it);
+ const MyMoneySecurity* security = dynamic_cast<const MyMoneySecurity *>(*it);
+ const MyMoneyInstitution* institution = dynamic_cast<const MyMoneyInstitution *>(*it);
+ const MyMoneySchedule* schedule = dynamic_cast<const MyMoneySchedule *>(*it);
+ delete *it;
+ if(account) {
+ const MyMoneyAccount& a = m_storage->account(id);
+ m_map[id] = new MyMoneyAccount(a);
+ } else if(security) {
+ const MyMoneySecurity& s = m_storage->security(id);
+ if(s.id().isEmpty()) {
+ const MyMoneySecurity& c = m_storage->currency(id);
+ m_map[id] = new MyMoneySecurity(c);
+ } else {
+ m_map[id] = new MyMoneySecurity(s);
+ }
+ } else if(payee) {
+ const MyMoneyPayee& p = m_storage->payee(id);
+ m_map[id] = new MyMoneyPayee(p);
+ } else if(institution) {
+ const MyMoneyInstitution& i = m_storage->institution(id);
+ m_map[id] = new MyMoneyInstitution(i);
+ } else if(schedule) {
+ const MyMoneySchedule& s = m_storage->schedule(id);
+ m_map[id] = new MyMoneySchedule(s);
+ } else {
+ qWarning("Ooops, should preload an unknown object with id '%s'", id.data());
+ }
+ return;
+ }
+}
+
+objectAccessMethod(schedule, MyMoneySchedule)
+objectAccessMethod(payee, MyMoneyPayee)
+objectAccessMethod(security, MyMoneySecurity)
+objectAccessMethod(institution, MyMoneyInstitution)
+
+preloadListMethod(Account, MyMoneyAccount)
+preloadListMethod(Payee, MyMoneyPayee)
+preloadListMethod(Institution, MyMoneyInstitution)
+preloadListMethod(Security, MyMoneySecurity)
+preloadListMethod(Schedule, MyMoneySchedule)
+
+preloadMethod(Account, MyMoneyAccount)
+preloadMethod(Security, MyMoneySecurity)
+preloadMethod(Payee, MyMoneyPayee)
+preloadMethod(Institution, MyMoneyInstitution)
+
+listMethod(payee, MyMoneyPayee)
+listMethod(institution, MyMoneyInstitution)
+
+#include "mymoneyobjectcontainer.moc"
diff --git a/kmymoney2/mymoney/mymoneyobjectcontainer.h b/kmymoney2/mymoney/mymoneyobjectcontainer.h
new file mode 100644
index 0000000..1f3b598
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyobjectcontainer.h
@@ -0,0 +1,99 @@
+/***************************************************************************
+ mymoneyobjectcontainer.h
+ -------------------
+ copyright : (C) 2006 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYOBJECTCONTAINER_H
+#define MYMONEYOBJECTCONTAINER_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qobject.h>
+#include <qstring.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include <kmymoney/export.h>
+#include <kmymoney/mymoneytransaction.h>
+#include <kmymoney/mymoneyaccount.h>
+#include <kmymoney/mymoneyinstitution.h>
+#include <kmymoney/mymoneypayee.h>
+#include <kmymoney/mymoneyobject.h>
+#include <kmymoney/mymoneysecurity.h>
+#include <kmymoney/imymoneystorage.h>
+
+/**
+ * @author Thomas Baumgart
+ */
+
+/**
+ * This class represents a generic container for all MyMoneyObject derived objects.
+ */
+class KMYMONEY_EXPORT MyMoneyObjectContainer : public QObject
+{
+ Q_OBJECT
+public:
+ MyMoneyObjectContainer();
+ ~MyMoneyObjectContainer();
+
+ const MyMoneyAccount& account(const QString& id);
+ const MyMoneyPayee& payee(const QString& id);
+ const MyMoneySecurity& security(const QString& id);
+ const MyMoneyInstitution& institution(const QString& id);
+ const MyMoneySchedule& schedule(const QString& id);
+
+ void account(QValueList<MyMoneyAccount>& list);
+ void payee(QValueList<MyMoneyPayee>& list);
+ void institution(QValueList<MyMoneyInstitution>& list);
+
+ void preloadAccount(const QValueList<MyMoneyAccount>& list);
+ void preloadPayee(const QValueList<MyMoneyPayee>& list);
+ void preloadInstitution(const QValueList<MyMoneyInstitution>& list);
+ void preloadSecurity(const QValueList<MyMoneySecurity>& list);
+ void preloadSchedule(const QValueList<MyMoneySchedule>& list);
+
+ void preloadAccount(const MyMoneyAccount& account);
+ void preloadSecurity(const MyMoneySecurity& security);
+ void preloadPayee(const MyMoneyPayee& payee);
+ void preloadInstitution(const MyMoneyInstitution& institution);
+
+ void clear(const QString& id);
+ void clear(IMyMoneyStorage* storage = 0);
+
+ const MyMoneyAccount& accountByName(const QString& name) const;
+
+ /**
+ * This method refreshes an already existing object in the container
+ * with a copy from the engine. The object is identified by its @a id.
+ * If the object is unknown or the @a id is empty, nothing is done.
+ */
+ void refresh(const QString& id);
+
+private:
+ void assignFraction(MyMoneyAccount* acc);
+
+private:
+ QMap<QString, MyMoneyObject const *> m_map;
+ IMyMoneyStorage* m_storage;
+};
+
+#endif
+
+
diff --git a/kmymoney2/mymoney/mymoneyobjecttest.cpp b/kmymoney2/mymoney/mymoneyobjecttest.cpp
new file mode 100644
index 0000000..f7201a9
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyobjecttest.cpp
@@ -0,0 +1,73 @@
+/***************************************************************************
+ mymoneyobjecttest.cpp
+ -------------------
+ copyright : (C) 2005 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "mymoneyobjecttest.h"
+#include "mymoneyaccount.h"
+
+MyMoneyObjectTest::MyMoneyObjectTest()
+{
+}
+
+
+void MyMoneyObjectTest::setUp () {
+}
+
+void MyMoneyObjectTest::tearDown () {
+}
+
+void MyMoneyObjectTest::testEmptyConstructor() {
+ MyMoneyAccount a;
+ CPPUNIT_ASSERT(a.id().isEmpty());
+}
+
+void MyMoneyObjectTest::testConstructor() {
+ MyMoneyAccount a(QString("thb"), MyMoneyAccount());
+
+ CPPUNIT_ASSERT(!a.id().isEmpty());
+ CPPUNIT_ASSERT(a.id() == QString("thb"));
+}
+
+void MyMoneyObjectTest::testClearId() {
+ MyMoneyAccount a(QString("thb"), MyMoneyAccount());
+
+ CPPUNIT_ASSERT(!a.id().isEmpty());
+ a.clearId();
+ CPPUNIT_ASSERT(a.id().isEmpty());
+}
+
+void MyMoneyObjectTest::testCopyConstructor() {
+ MyMoneyAccount a(QString("thb"), MyMoneyAccount());
+ MyMoneyAccount b(a);
+
+ CPPUNIT_ASSERT(a.MyMoneyObject::operator==(b));
+}
+
+void MyMoneyObjectTest::testAssignmentConstructor() {
+ MyMoneyAccount a(QString("thb"), MyMoneyAccount());
+ MyMoneyAccount b = a;
+
+ CPPUNIT_ASSERT(a.MyMoneyObject::operator==(b));
+}
+
+void MyMoneyObjectTest::testEquality() {
+ MyMoneyAccount a(QString("thb"), MyMoneyAccount());
+ MyMoneyAccount b(QString("thb"), MyMoneyAccount());
+ MyMoneyAccount c(QString("ace"), MyMoneyAccount());
+
+ CPPUNIT_ASSERT(a.MyMoneyObject::operator==(b));
+ CPPUNIT_ASSERT(!(a.MyMoneyObject::operator==(c)));
+}
+
diff --git a/kmymoney2/mymoney/mymoneyobjecttest.h b/kmymoney2/mymoney/mymoneyobjecttest.h
new file mode 100644
index 0000000..0b99671
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyobjecttest.h
@@ -0,0 +1,50 @@
+/***************************************************************************
+ mymoneyobjecttest.h
+ -------------------
+ copyright : (C) 2005 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 __MYMONEYOBJECTTEST_H__
+#define __MYMONEYOBJECTTEST_H__
+
+#include <cppunit/extensions/HelperMacros.h>
+
+#define private public
+#include "mymoneyobject.h"
+#undef private
+
+class MyMoneyObjectTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(MyMoneyObjectTest);
+ CPPUNIT_TEST(testEmptyConstructor);
+ CPPUNIT_TEST(testConstructor);
+ CPPUNIT_TEST(testClearId);
+ CPPUNIT_TEST(testCopyConstructor);
+ CPPUNIT_TEST(testAssignmentConstructor);
+ CPPUNIT_TEST(testEquality);
+ CPPUNIT_TEST_SUITE_END();
+
+protected:
+
+public:
+ MyMoneyObjectTest();
+ void setUp ();
+ void tearDown ();
+ void testEmptyConstructor();
+ void testConstructor();
+ void testClearId();
+ void testCopyConstructor();
+ void testAssignmentConstructor();
+ void testEquality();
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneyobserver.cpp b/kmymoney2/mymoney/mymoneyobserver.cpp
new file mode 100644
index 0000000..f343003
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyobserver.cpp
@@ -0,0 +1,30 @@
+/***************************************************************************
+ mymoneyobserver.cpp - description
+ -------------------
+ begin : Sat May 18 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "mymoneyobserver.h"
+
+MyMoneyObserver::MyMoneyObserver()
+{
+}
+MyMoneyObserver::~MyMoneyObserver()
+{
+}
diff --git a/kmymoney2/mymoney/mymoneyobserver.h b/kmymoney2/mymoney/mymoneyobserver.h
new file mode 100644
index 0000000..3e6c922
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyobserver.h
@@ -0,0 +1,55 @@
+/***************************************************************************
+ mymoneyobserver.h - description
+ -------------------
+ begin : Sat May 18 2002
+ copyright : (C) 2000-2005 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYOBSERVER_H
+#define MYMONEYOBSERVER_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include <kmymoney/export.h>
+class MyMoneySubject;
+class QString;
+
+/**
+ * This is the base class to be used to construct an
+ * observer for usage in a subject/observer relationship
+ *
+ * @author Thomas Baumgart
+ */
+class KMYMONEY_EXPORT MyMoneyObserver {
+public:
+ virtual ~MyMoneyObserver();
+ virtual void update(const QString& id) = 0;
+
+protected:
+ MyMoneyObserver();
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneyobservertest.h b/kmymoney2/mymoney/mymoneyobservertest.h
new file mode 100644
index 0000000..a806882
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyobservertest.h
@@ -0,0 +1,106 @@
+/***************************************************************************
+ mymoneyobservertest.h
+ -------------------
+ copyright : (C) 2002 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 __MYMONEYOBSERVERTEST_H__
+#define __MYMONEYOBSERVERTEST_H__
+
+#include <cppunit/extensions/HelperMacros.h>
+
+class TestObserver : public MyMoneyObserver
+{
+public:
+ TestObserver() { m_updated = ""; }
+ void update(const QString& id) { m_updated = id; };
+ const QString& updated(void) { return m_updated; };
+ void reset(void) { m_updated = ""; };
+private:
+ QString m_updated;
+};
+
+class TestSubject : public MyMoneySubject
+{
+};
+
+class MyMoneyObserverTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(MyMoneyObserverTest);
+ CPPUNIT_TEST(testEmptySubject);
+ CPPUNIT_TEST(testAddObserver);
+ CPPUNIT_TEST(testRemoveObserver);
+ CPPUNIT_TEST(testNotifyObserver);
+ CPPUNIT_TEST(testNotifyMultipleObserver);
+ CPPUNIT_TEST_SUITE_END();
+
+protected:
+ TestSubject *subject;
+ TestObserver *observer1;
+ TestObserver *observer2;
+
+public:
+ MyMoneyObserverTest () {}
+
+
+void setUp () {
+ subject = new TestSubject;
+ observer1 = new TestObserver;
+ observer2 = new TestObserver;
+}
+
+void tearDown () {
+ delete observer1;
+ delete observer2;
+ delete subject;
+}
+
+void testEmptySubject() {
+ CPPUNIT_ASSERT(subject->m_observers.count() == 0);
+}
+
+void testAddObserver() {
+ subject->attach(observer1);
+ CPPUNIT_ASSERT(subject->m_observers.count() == 1);
+ CPPUNIT_ASSERT(subject->m_observers.at(0) == observer1);
+}
+
+void testRemoveObserver() {
+ testAddObserver();
+ subject->detach(observer1);
+ CPPUNIT_ASSERT(subject->m_observers.count() == 0);
+}
+
+void testNotifyObserver() {
+ testAddObserver();
+ CPPUNIT_ASSERT(observer1->updated() == "");
+ subject->notify("my id");
+ CPPUNIT_ASSERT(observer1->updated() == "my id");
+}
+
+void testNotifyMultipleObserver() {
+ testAddObserver();
+ subject->attach(observer2);
+ CPPUNIT_ASSERT(subject->m_observers.count() == 2);
+ CPPUNIT_ASSERT(subject->m_observers.at(0) == observer1);
+ CPPUNIT_ASSERT(subject->m_observers.at(1) == observer2);
+
+ CPPUNIT_ASSERT(observer1->updated() == "");
+ CPPUNIT_ASSERT(observer2->updated() == "");
+ subject->notify("my id");
+ CPPUNIT_ASSERT(observer1->updated() == "my id");
+ CPPUNIT_ASSERT(observer2->updated() == "my id");
+}
+
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneypayee.cpp b/kmymoney2/mymoney/mymoneypayee.cpp
new file mode 100644
index 0000000..cbfbbb7
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneypayee.cpp
@@ -0,0 +1,220 @@
+/***************************************************************************
+ mymoneypayee.cpp
+ -------------------
+ copyright : (C) 2000 by Michael Edwardes
+ (C) 2008 by Thomas Baumgart
+ email : mte@users.sourceforge.net
+ ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstringlist.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "mymoneypayee.h"
+#include "mymoneyutils.h"
+#include <kmymoney/mymoneyexception.h>
+
+MyMoneyPayee MyMoneyPayee::null;
+
+MyMoneyPayee::MyMoneyPayee() :
+ m_matchingEnabled(false),
+ m_usingMatchKey(false),
+ m_matchKeyIgnoreCase(true)
+{
+}
+
+MyMoneyPayee::MyMoneyPayee(const QString& id, const MyMoneyPayee& payee) :
+ m_matchingEnabled(false),
+ m_usingMatchKey(false),
+ m_matchKeyIgnoreCase(true)
+{
+ *this = payee;
+ m_id = id;
+}
+
+MyMoneyPayee::MyMoneyPayee(const QString& name, const QString& address,
+ const QString& city, const QString& state, const QString& postcode,
+ const QString& telephone, const QString& email) :
+ m_matchingEnabled(false),
+ m_usingMatchKey(false),
+ m_matchKeyIgnoreCase(true)
+{
+ m_name = name;
+ m_address = address;
+ m_city = city;
+ m_state = state;
+ m_postcode = postcode;
+ m_telephone = telephone;
+ m_email = email;
+}
+
+MyMoneyPayee::MyMoneyPayee(const QDomElement& node) :
+ MyMoneyObject(node)
+{
+ if ("PAYEE" != node.tagName()) {
+ throw new MYMONEYEXCEPTION("Node was not PAYEE");
+ }
+
+ m_name = node.attribute("name");
+ m_reference = node.attribute("reference");
+ m_email = node.attribute("email");
+
+ m_matchingEnabled = node.attribute("matchingenabled","0").toUInt();
+ if ( m_matchingEnabled )
+ {
+ m_usingMatchKey = node.attribute("usingmatchkey","0").toUInt();
+ m_matchKeyIgnoreCase = node.attribute("matchignorecase","0").toUInt();
+ m_matchKey = node.attribute("matchkey");
+ }
+
+ if(node.hasAttribute("notes")) {
+ m_notes = node.attribute("notes");
+ }
+
+ if (node.hasAttribute("defaultaccountid")) {
+ m_defaultAccountId = node.attribute("defaultaccountid");
+ }
+
+ QDomNodeList nodeList = node.elementsByTagName("ADDRESS");
+ if(nodeList.count() == 0) {
+ QString msg = QString("No ADDRESS in payee %1").arg(m_name);
+ throw new MYMONEYEXCEPTION(msg);
+ }
+
+ QDomElement addrNode = nodeList.item(0).toElement();
+ m_address = addrNode.attribute("street");
+ m_city = addrNode.attribute("city");
+ m_postcode = addrNode.attribute("postcode");
+ m_state = addrNode.attribute("state");
+ m_telephone = addrNode.attribute("telephone");
+}
+
+MyMoneyPayee::~MyMoneyPayee()
+{
+}
+
+MyMoneyPayee::MyMoneyPayee(const MyMoneyPayee& right) :
+ MyMoneyObject(right)
+{
+ *this = right;
+}
+
+bool MyMoneyPayee::operator == (const MyMoneyPayee& right) const
+{
+ return (MyMoneyObject::operator==(right) &&
+ ((m_name.length() == 0 && right.m_name.length() == 0) || (m_name == right.m_name)) &&
+ ((m_address.length() == 0 && right.m_address.length() == 0) || (m_address == right.m_address)) &&
+ ((m_city.length() == 0 && right.m_city.length() == 0) || (m_city == right.m_city)) &&
+ ((m_state.length() == 0 && right.m_state.length() == 0) || (m_state == right.m_state)) &&
+ ((m_postcode.length() == 0 && right.m_postcode.length() == 0) || (m_postcode == right.m_postcode)) &&
+ ((m_telephone.length() == 0 && right.m_telephone.length() == 0) || (m_telephone == right.m_telephone)) &&
+ ((m_email.length() == 0 && right.m_email.length() == 0) || (m_email == right.m_email)) &&
+ (m_matchingEnabled == right.m_matchingEnabled) &&
+ (m_usingMatchKey == right.m_usingMatchKey) &&
+ (m_matchKeyIgnoreCase == right.m_matchKeyIgnoreCase) &&
+ ((m_matchKey.length() == 0 && right.m_matchKey.length() == 0) || m_matchKey == right.m_matchKey) &&
+ ((m_reference.length() == 0 && right.m_reference.length() == 0) || (m_reference == right.m_reference)) &&
+ ((m_defaultAccountId.length() == 0 && right.m_defaultAccountId.length() == 0) || m_defaultAccountId == right.m_defaultAccountId) );
+}
+
+void MyMoneyPayee::writeXML(QDomDocument& document, QDomElement& parent) const
+{
+ QDomElement el = document.createElement("PAYEE");
+
+ writeBaseXML(document, el);
+
+ el.setAttribute("name", m_name);
+ el.setAttribute("reference", m_reference);
+ el.setAttribute("email", m_email);
+ if(!m_notes.isEmpty())
+ el.setAttribute("notes", m_notes);
+
+ el.setAttribute("matchingenabled", m_matchingEnabled);
+ if ( m_matchingEnabled )
+ {
+ el.setAttribute("usingmatchkey", m_usingMatchKey);
+ el.setAttribute("matchignorecase", m_matchKeyIgnoreCase);
+ el.setAttribute("matchkey", m_matchKey);
+ }
+
+ if (!m_defaultAccountId.isEmpty()) {
+ el.setAttribute("defaultaccountid", m_defaultAccountId);
+ }
+
+ QDomElement address = document.createElement("ADDRESS");
+ address.setAttribute("street", m_address);
+ address.setAttribute("city", m_city);
+ address.setAttribute("postcode", m_postcode);
+ address.setAttribute("state", m_state);
+ address.setAttribute("telephone", m_telephone);
+
+ el.appendChild(address);
+
+ parent.appendChild(el);
+}
+
+bool MyMoneyPayee::hasReferenceTo(const QString& id) const
+{
+ return id == m_defaultAccountId;
+
+}
+
+MyMoneyPayee::payeeMatchType MyMoneyPayee::matchData(bool& ignorecase, QStringList& keys) const
+{
+ payeeMatchType type = matchDisabled;
+ keys.clear();
+ ignorecase = m_matchKeyIgnoreCase;
+
+ if ( m_matchingEnabled )
+ {
+ type = m_usingMatchKey ? matchKey : matchName;
+ if(type == matchKey)
+ keys = QStringList::split(";", m_matchKey);
+ }
+
+ return type;
+}
+
+MyMoneyPayee::payeeMatchType MyMoneyPayee::matchData(bool& ignorecase, QString& keyString) const
+{
+ QStringList keys;
+ payeeMatchType type = matchData(ignorecase, keys);
+ keyString = keys.join(";");
+ return type;
+}
+
+void MyMoneyPayee::setMatchData(payeeMatchType type, bool ignorecase, const QStringList& keys)
+{
+ m_matchingEnabled = (type != matchDisabled);
+ m_matchKeyIgnoreCase = ignorecase;
+ m_matchKey = QString();
+
+ if ( m_matchingEnabled )
+ {
+ m_usingMatchKey = (type == matchKey);
+ if ( m_usingMatchKey ) {
+ m_matchKey = keys.join(";");
+ }
+ }
+}
+
+void MyMoneyPayee::setMatchData(payeeMatchType type, bool ignorecase, const QString& keys)
+{
+ setMatchData(type, ignorecase, QStringList::split(";", keys));
+}
+
+// vim:cin:si:ai:et:ts=2:sw=2:
diff --git a/kmymoney2/mymoney/mymoneypayee.h b/kmymoney2/mymoney/mymoneypayee.h
new file mode 100644
index 0000000..c2c711b
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneypayee.h
@@ -0,0 +1,206 @@
+/***************************************************************************
+ mymoneypayee.h
+ -------------------
+ copyright : (C) 2000 by Michael Edwardes
+ 2005 by Thomas Baumgart
+ email : mte@users.sourceforge.net
+ ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYPAYEE_H
+#define MYMONEYPAYEE_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstring.h>
+class QStringList;
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include <kmymoney/export.h>
+#include <kmymoney/mymoneyobject.h>
+
+/**
+ * This class represents a payee or receiver within the MyMoney engine.
+ * Since it is not payee-specific, it is also used as a generic address
+ * book entry.
+ *
+ * @author Thomas Baumgart
+ */
+class KMYMONEY_EXPORT MyMoneyPayee : public MyMoneyObject
+{
+private:
+ // Simple fields
+ QString m_name;
+ QString m_address;
+ QString m_city;
+ QString m_state;
+ QString m_postcode;
+ QString m_telephone;
+ QString m_email;
+ QString m_notes;
+
+ // Transaction matching fields
+ bool m_matchingEnabled; //< Whether this payee should be matched at all
+ bool m_usingMatchKey; //< If so, whether a m_matchKey list is used (true), or just m_name is used (false)
+ bool m_matchKeyIgnoreCase; //< Whether to ignore the case of the match key or name
+
+ /**
+ * Semicolon separated list of matching keys used when trying to find a suitable
+ * payee for imported transactions.
+ */
+ QString m_matchKey;
+
+ // Category (account) matching fields
+ QString m_defaultAccountId;
+
+ /**
+ * This member keeps a reference to an external database
+ * (e.g. kaddressbook). It is the responsability of the
+ * application to format the reference string
+ * (e.g. encoding the name of the external database into the
+ * reference string).
+ * If no external database is available it should be kept
+ * empty by the application.
+ */
+ QString m_reference;
+
+public:
+ typedef enum {
+ matchDisabled = 0,
+ matchName,
+ matchKey
+ } payeeMatchType;
+
+ MyMoneyPayee();
+ MyMoneyPayee(const QString& id, const MyMoneyPayee& payee);
+ MyMoneyPayee(const QString& name,
+ const QString& address=QString::null,
+ const QString& city=QString::null,
+ const QString& state=QString::null,
+ const QString& postcode=QString::null,
+ const QString& telephone=QString::null,
+ const QString& email=QString::null);
+ /**
+ * This is the constructor for a payee that is described by a
+ * QDomElement (e.g. from a file).
+ *
+ * @param el const reference to the QDomElement from which to
+ * create the object
+ */
+ MyMoneyPayee(const QDomElement& el);
+
+ ~MyMoneyPayee();
+
+ // Simple get operations
+ QString name(void) const { return m_name; }
+ QString address(void) const { return m_address; }
+ QString city(void) const { return m_city; }
+ QString state(void) const { return m_state; }
+ QString postcode(void) const { return m_postcode; }
+ QString telephone(void) const { return m_telephone; }
+ QString email(void) const { return m_email; }
+ QString notes(void) const { return m_notes; }
+
+ const QString id(void) const { return m_id; };
+ const QString reference(void) const { return m_reference; };
+
+ // Simple set operations
+ void setName(const QString& val) { m_name = val; }
+ void setAddress(const QString& val) { m_address = val; }
+ void setCity(const QString& val) { m_city = val; }
+ void setState(const QString& val) { m_state = val; }
+ void setPostcode(const QString& val) { m_postcode = val; }
+ void setTelephone(const QString& val) { m_telephone = val; }
+ void setEmail(const QString& val) { m_email = val; }
+ void setNotes(const QString& val) { m_notes = val; }
+ void setReference(const QString& ref) { m_reference = ref; }
+
+ /**
+ * Get all match data in one call
+ *
+ * @param ignorecase Bool which will be replaced to indicate whether the match is
+ * case-sensitive (false) or case-insensitive (true)
+ * @param keys List of strings which will be replaced by the match key to use for this payee
+ *
+ * @return the matching type (see payeeMatchType for details)
+ */
+ payeeMatchType matchData(bool& ignorecase, QStringList& keys) const;
+
+ /**
+ * Set all match data in one call
+ *
+ * @param type matching type (see payeeMatchType for details)
+ * @param ignorecase Whether case should be ignored for the key/name match
+ * @param keys A list of keys themselves, if applicable
+ */
+ void setMatchData(payeeMatchType type, bool ignorecase, const QStringList& keys);
+
+ /**
+ * Get all match data in one call (overloaded version for database module)
+ *
+ * @param ignorecase Bool which will be replaced to indicate whether the match is
+ * case-sensitive (false) or case-insensitive (true)
+ * @param keyString A list of keys in single-string format, if applicable
+ *
+ * @return the matching type (see payeeMatchType for details)
+ */
+ payeeMatchType matchData(bool& ignorecase, QString& keyString) const;
+
+ /**
+ * Set all match data in one call (overloaded version for database module)
+ *
+ * @param type matching type (see payeeMatchType for details)
+ * @param ignorecase Whether case should be ignored for the key/name match
+ * @param keys A list of keys in single-string format, if applicable
+ */
+ void setMatchData(payeeMatchType type, bool ignorecase, const QString& keys);
+
+
+ bool defaultAccountEnabled() const { return !m_defaultAccountId.isEmpty(); }
+ const QString& defaultAccountId() const { return m_defaultAccountId; }
+ void setDefaultAccountId(const QString& id = QString()) {
+ m_defaultAccountId = id;
+ }
+
+ // Copy constructors
+ MyMoneyPayee(const MyMoneyPayee&);
+
+ // Equality operator
+ bool operator == (const MyMoneyPayee &) const;
+
+ void writeXML(QDomDocument& document, QDomElement& parent) const;
+
+ /**
+ * This method checks if a reference to the given object exists. It returns,
+ * a @p true if the object is referencing the one requested by the
+ * parameter @p id. If it does not, this method returns @p false.
+ *
+ * @param id id of the object to be checked for references
+ * @retval true This object references object with id @p id.
+ * @retval false This object does not reference the object with id @p id.
+ */
+ virtual bool hasReferenceTo(const QString& id) const;
+
+ static MyMoneyPayee null;
+};
+
+inline bool operator==(const MyMoneyPayee& lhs, const QString& rhs) { return lhs.id() == rhs; }
+
+#endif
+// vim:cin:si:ai:et:ts=2:sw=2:
diff --git a/kmymoney2/mymoney/mymoneypayeetest.cpp b/kmymoney2/mymoney/mymoneypayeetest.cpp
new file mode 100644
index 0000000..601679c
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneypayeetest.cpp
@@ -0,0 +1,76 @@
+/***************************************************************************
+ mymoneypayeetest.cpp
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "mymoneypayeetest.h"
+#include <iostream>
+#include <fstream>
+using namespace std;
+
+MyMoneyPayeeTest:: MyMoneyPayeeTest () {
+}
+
+void MyMoneyPayeeTest::setUp () {
+}
+
+void MyMoneyPayeeTest::tearDown () {
+}
+
+void MyMoneyPayeeTest::testXml(){
+ QDomDocument doc;
+ QDomElement parent = doc.createElement("Test");
+ doc.appendChild(parent);
+ MyMoneyPayee payee1;
+ payee1.m_id = "some random id";//if the ID isn't set, w ethrow an exception
+ payee1.writeXML(doc,parent);
+ QString temp1 = "Account1";
+ payee1.setDefaultAccountId(temp1);
+ payee1.writeXML(doc,parent);
+ QString temp2 = "Account2";
+ payee1.setDefaultAccountId(temp2);
+ payee1.writeXML(doc,parent);
+ payee1.setDefaultAccountId();
+ payee1.writeXML(doc,parent);
+ QDomElement el = parent.firstChild().toElement();
+ CPPUNIT_ASSERT(!el.isNull());
+ MyMoneyPayee payee2(el);
+ CPPUNIT_ASSERT(!payee2.defaultAccountEnabled());
+ CPPUNIT_ASSERT(payee2.defaultAccountId().isEmpty());
+ el = el.nextSibling().toElement();
+ CPPUNIT_ASSERT(!el.isNull());
+ MyMoneyPayee payee3(el);
+ CPPUNIT_ASSERT(payee3.defaultAccountEnabled());
+ CPPUNIT_ASSERT(payee3.defaultAccountId()==temp1);
+ el = el.nextSibling().toElement();
+ CPPUNIT_ASSERT(!el.isNull());
+ MyMoneyPayee payee4(el);
+ CPPUNIT_ASSERT(payee4.defaultAccountEnabled());
+ CPPUNIT_ASSERT(payee4.defaultAccountId()==temp2);
+ el = el.nextSibling().toElement();
+ CPPUNIT_ASSERT(!el.isNull());
+ MyMoneyPayee payee5(el);
+ CPPUNIT_ASSERT(!payee5.defaultAccountEnabled());
+ CPPUNIT_ASSERT(payee5.defaultAccountId().isEmpty());
+}
+
+void MyMoneyPayeeTest::testDefaultAccount(){
+ MyMoneyPayee payee;
+ CPPUNIT_ASSERT(!payee.defaultAccountEnabled());
+ CPPUNIT_ASSERT(payee.defaultAccountId().isEmpty());
+ QString temp = "Account1";
+ payee.setDefaultAccountId(temp);
+ CPPUNIT_ASSERT(payee.defaultAccountEnabled());
+ CPPUNIT_ASSERT(payee.defaultAccountId()==temp);
+ payee.setDefaultAccountId();
+ CPPUNIT_ASSERT(!payee.defaultAccountEnabled());
+ CPPUNIT_ASSERT(payee.defaultAccountId().isEmpty());
+}
diff --git a/kmymoney2/mymoney/mymoneypayeetest.h b/kmymoney2/mymoney/mymoneypayeetest.h
new file mode 100644
index 0000000..baef561
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneypayeetest.h
@@ -0,0 +1,41 @@
+/***************************************************************************
+ mymoneypayeetest.h
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 __MYMONEYPAYEETEST_H__
+#define __MYMONEYPAYEETEST_H__
+
+#include <cppunit/extensions/HelperMacros.h>
+#include "autotest.h"
+
+#define private public
+#define protected public
+#include "mymoneypayee.h"
+#undef private
+#undef protected
+
+class MyMoneyPayeeTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(MyMoneyPayeeTest);
+ CPPUNIT_TEST(testXml);
+ CPPUNIT_TEST(testDefaultAccount);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ MyMoneyPayeeTest ();
+
+ void setUp ();
+ void tearDown ();
+ void testXml();
+ void testDefaultAccount();
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneyprice.cpp b/kmymoney2/mymoney/mymoneyprice.cpp
new file mode 100644
index 0000000..b674776
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyprice.cpp
@@ -0,0 +1,113 @@
+/***************************************************************************
+ mymoneyprice - description
+ -------------------
+ begin : Sun Nov 21 2004
+ copyright : (C) 2000-2004 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+/**
+ * @author Thomas Baumgart
+ */
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+// ----------------------------------------------------------------------------
+// KDE Includes
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "mymoneyprice.h"
+#include "mymoneyexception.h"
+
+MyMoneyPrice::MyMoneyPrice() :
+ m_date(QDate())
+{
+}
+
+MyMoneyPrice::MyMoneyPrice(const QString& from, const QString& to, const QDomElement& node)
+{
+ if("PRICE" != node.tagName())
+ throw new MYMONEYEXCEPTION("Node was not PRICE");
+
+ m_fromSecurity = from;
+ m_toSecurity = to;
+
+ m_date = QDate::fromString(node.attribute("date"), Qt::ISODate);
+ m_rate = MyMoneyMoney(node.attribute("price"));
+ m_source = node.attribute("source");
+
+ if(!m_rate.isZero())
+ m_invRate = MyMoneyMoney(1,1) / m_rate;
+ else
+ qDebug("Price with zero value loaded");
+}
+
+MyMoneyPrice::MyMoneyPrice(const QString& from, const QString& to, const QDate& date, const MyMoneyMoney& rate, const QString& source) :
+ m_fromSecurity(from),
+ m_toSecurity(to),
+ m_date(date),
+ m_rate(rate),
+ m_source(source)
+{
+ if(!m_rate.isZero())
+ m_invRate = MyMoneyMoney(1,1) / m_rate;
+ else
+ qDebug("Price with zero value created");
+}
+
+MyMoneyPrice::~MyMoneyPrice()
+{
+}
+
+const MyMoneyMoney MyMoneyPrice::rate(const QString& id) const
+{
+ static MyMoneyMoney dummyPrice(1,1);
+
+ if(!isValid())
+ return dummyPrice;
+
+ if(id.isEmpty() || id == m_toSecurity)
+ return m_rate;
+ if(id == m_fromSecurity)
+ return m_invRate;
+
+ QString msg = QString("Unknown security id %1 for price info %2/%3.").arg(id).arg(m_fromSecurity).arg(m_toSecurity);
+ throw new MYMONEYEXCEPTION(msg);
+}
+
+bool MyMoneyPrice::isValid(void) const
+{
+ return (m_date.isValid() && !m_fromSecurity.isEmpty() && !m_toSecurity.isEmpty());
+}
+
+// Equality operator
+bool MyMoneyPrice::operator == (const MyMoneyPrice &right) const
+{
+ return ((m_date == right.m_date) &&
+ (m_rate == right.m_rate) &&
+ ((m_fromSecurity.length() == 0 && right.m_fromSecurity.length() == 0) || (m_fromSecurity == right.m_fromSecurity)) &&
+ ((m_toSecurity.length() == 0 && right.m_toSecurity.length() == 0) || (m_toSecurity == right.m_toSecurity)) &&
+ ((m_source.length() == 0 && right.m_source.length() == 0) || (m_source == right.m_source)));
+}
+
+bool MyMoneyPrice::hasReferenceTo(const QString& id) const
+{
+ return (id == m_fromSecurity) || (id == m_toSecurity);
+}
diff --git a/kmymoney2/mymoney/mymoneyprice.h b/kmymoney2/mymoney/mymoneyprice.h
new file mode 100644
index 0000000..faf6454
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyprice.h
@@ -0,0 +1,158 @@
+/***************************************************************************
+ mymoneyprice - description
+ -------------------
+ begin : Sun Nov 21 2004
+ copyright : (C) 2000-2004 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYPRICE_H
+#define MYMONEYPRICE_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstring.h>
+#include <qdatetime.h>
+#include <qpair.h>
+#include <qmap.h>
+#include <qdom.h>
+
+// ----------------------------------------------------------------------------
+// KDE Includes
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include <kmymoney/mymoneymoney.h>
+#include <kmymoney/export.h>
+
+/**
+ * @author Thomas Baumgart
+ */
+
+/**
+ * This class represents an exchange rate of a security, currency or commodity
+ * based on another security, currency or commodity for a specific date.
+ * The term security is used in this class as a placeholder for all
+ * those previously mentioned items.
+ * In general, the other security is a currency.
+ *
+ * The securities and the rate form the following equation:
+ *
+ * @code
+ *
+ * toSecurity = rate * fromSecurity
+ *
+ * @endcode
+ *
+ * Using the @p rate() member function, one can retrieve the conversion rate based
+ * upon the @p toSecurity or the @p fromSecurity.
+ */
+class KMYMONEY_EXPORT MyMoneyPrice
+{
+public:
+ MyMoneyPrice();
+ MyMoneyPrice(const QString& from, const QString& to, const QDomElement& node);
+ MyMoneyPrice(const QString& from, const QString& to, const QDate& date, const MyMoneyMoney& rate, const QString& source = QString());
+ virtual ~MyMoneyPrice();
+
+ /**
+ * This method returns the price information based on the
+ * security referenced by @p id. If @p id is empty (default), the
+ * price is returned based on the toSecurity. If this price
+ * object is invalid (see isValid()) MyMoneyMoney(1,1) is returned.
+ *
+ * @param id return price to be the factor to be used to convert a value into
+ * the correcponding value in security @p id.
+ *
+ * @return returns the exchange rate (price) as MyMoneyMoney object.
+ *
+ * If @p id is not empty and does not match either security ids of this price
+ * an exception will be thrown.
+ *
+ * Example:
+ * Assume the following code, where you have a price object and
+ * and you wish to convert from an amount in GBP (@p valGBP) to ADF (@p valADF).
+ * Then your code will look like this:
+ *
+ * @code
+ *
+ * MyMoneyPrice price("ADF", "GBP", QDate(2005,9,20), MyMoneyMoney(1,3), "User");
+ * MyMoneyMoney valADF, valGBP(100,1);
+ *
+ * valADF = valGBP * price.rate("ADF");
+ *
+ * @endcode
+ *
+ * valADF will contain the value 300 after the assignment operation, because @p price.rate("ADF") returned
+ * @p 3/1 even though the price information kept with the object was @p 1/3, but based on the other
+ * conversion direction (from ADF to GBP).
+ */
+ const MyMoneyMoney rate(const QString& id) const;
+
+ const QDate& date(void) const { return m_date; };
+ const QString& source(void) const { return m_source; };
+ const QString& from(void) const { return m_fromSecurity; };
+ const QString& to(void) const { return m_toSecurity; };
+
+ /**
+ * Check whether the object is valid or not. A MyMoneyPrice object
+ * is valid if the date is valid and both security ids are set. In case
+ * of an invalid object, price() always returns 1.
+ *
+ * @retval true if price object is valid
+ * @retval false if price object is not valid
+ */
+ bool isValid(void) const;
+
+ // Equality operator
+ bool operator == (const MyMoneyPrice &) const;
+
+ // Inequality operator
+ bool operator != (const MyMoneyPrice &right) const { return !(operator == (right)); };
+
+ /**
+ * This method checks if a reference to the given object exists. It returns,
+ * a @p true if the object is referencing the one requested by the
+ * parameter @p id. If it does not, this method returns @p false.
+ *
+ * @param id id of the object to be checked for references
+ * @retval true This object references object with id @p id.
+ * @retval false This object does not reference the object with id @p id.
+ */
+ bool hasReferenceTo(const QString& id) const;
+
+private:
+ QString m_fromSecurity;
+ QString m_toSecurity;
+ QDate m_date;
+ MyMoneyMoney m_rate;
+ MyMoneyMoney m_invRate;
+ QString m_source;
+};
+
+
+typedef QPair<QString, QString> MyMoneySecurityPair;
+typedef QMap<QDate, MyMoneyPrice> MyMoneyPriceEntries;
+typedef QMap<MyMoneySecurityPair, MyMoneyPriceEntries> MyMoneyPriceList;
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneypricetest.cpp b/kmymoney2/mymoney/mymoneypricetest.cpp
new file mode 100644
index 0000000..203e9ca
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneypricetest.cpp
@@ -0,0 +1,90 @@
+/***************************************************************************
+ mymoneypricetest.cpp
+ -------------------
+ copyright : (C) 2005 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "mymoneypricetest.h"
+#include "mymoneyexception.h"
+
+MyMoneyPriceTest::MyMoneyPriceTest()
+{
+}
+
+
+void MyMoneyPriceTest::setUp()
+{
+ m = new MyMoneyPrice();
+}
+
+void MyMoneyPriceTest::tearDown()
+{
+ delete m;
+}
+
+void MyMoneyPriceTest::testDefaultConstructor()
+{
+ CPPUNIT_ASSERT(m->isValid() == false);
+}
+
+void MyMoneyPriceTest::testConstructor()
+{
+ MyMoneyPrice n(QString("from"), QString("to"), QDate(2005,9,23), MyMoneyMoney(1,3), QString("MySource"));
+
+ CPPUNIT_ASSERT(n.isValid() == true);
+ CPPUNIT_ASSERT(n.from() == QString("from"));
+ CPPUNIT_ASSERT(n.to() == QString("to"));
+ CPPUNIT_ASSERT(n.date() == QDate(2005,9,23));
+ CPPUNIT_ASSERT(n.source() == QString("MySource"));
+ CPPUNIT_ASSERT(n.rate("to") == MyMoneyMoney(1,3));
+}
+
+void MyMoneyPriceTest::testValidity()
+{
+ QString emptyId;
+ MyMoneyPrice n1(emptyId, QString("to"), QDate(2005,9,23), MyMoneyMoney(1,3), QString("MySource"));
+ MyMoneyPrice n2(QString("from"), emptyId, QDate(2005,9,23), MyMoneyMoney(1,3), QString("MySource"));
+ MyMoneyPrice n3(QString("from"), QString("to"), QDate(), MyMoneyMoney(1,3), QString("MySource"));
+ MyMoneyPrice n4(QString("from"), QString("to"), QDate(2005,9,23), MyMoneyMoney(1,3), QString("MySource"));
+
+ CPPUNIT_ASSERT(n1.isValid() == false);
+ CPPUNIT_ASSERT(n2.isValid() == false);
+ CPPUNIT_ASSERT(n3.isValid() == false);
+ CPPUNIT_ASSERT(n4.isValid() == true);
+}
+
+void MyMoneyPriceTest::testRate()
+{
+ MyMoneyPrice n1(QString("from"), QString("to"), QDate(2005,9,23), MyMoneyMoney(1,3), QString("MySource"));
+ MyMoneyPrice n2(QString("from"), QString("to"), QDate(), MyMoneyMoney(1,3), QString("MySource"));
+
+ try {
+ CPPUNIT_ASSERT(n1.rate("to") == MyMoneyMoney(1,3));
+ CPPUNIT_ASSERT(n1.rate("from") == MyMoneyMoney(3,1));
+ CPPUNIT_ASSERT(n1.rate(QString()) == MyMoneyMoney(1,3));
+
+ CPPUNIT_ASSERT(n2.isValid() == false);
+ CPPUNIT_ASSERT(n2.rate("to") == MyMoneyMoney(1,1));
+ } catch(MyMoneyException *e) {
+ CPPUNIT_FAIL("Unexpected exception");
+ delete e;
+ }
+
+ try {
+ n1.rate("unknown");
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+}
+
diff --git a/kmymoney2/mymoney/mymoneypricetest.h b/kmymoney2/mymoney/mymoneypricetest.h
new file mode 100644
index 0000000..a153a94
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneypricetest.h
@@ -0,0 +1,48 @@
+/***************************************************************************
+ mymoneypricetest.h
+ -------------------
+ copyright : (C) 2005 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 __MYMONEYPRICETEST_H__
+#define __MYMONEYPRICETEST_H__
+
+#include <cppunit/extensions/HelperMacros.h>
+
+#define private public
+#include "mymoneyprice.h"
+#undef private
+
+class MyMoneyPriceTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(MyMoneyPriceTest);
+ CPPUNIT_TEST(testDefaultConstructor);
+ CPPUNIT_TEST(testConstructor);
+ CPPUNIT_TEST(testValidity);
+ CPPUNIT_TEST_SUITE_END();
+
+protected:
+ MyMoneyPrice* m;
+
+public:
+ MyMoneyPriceTest();
+
+ void setUp();
+ void tearDown();
+
+ void testDefaultConstructor();
+ void testConstructor();
+ void testValidity();
+ void testRate();
+
+};
+#endif
diff --git a/kmymoney2/mymoney/mymoneyreport.cpp b/kmymoney2/mymoney/mymoneyreport.cpp
new file mode 100644
index 0000000..63c4ca7
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyreport.cpp
@@ -0,0 +1,787 @@
+/***************************************************************************
+ mymoneyreport.cpp
+ -------------------
+ begin : Sun July 4 2004
+ copyright : (C) 2004-2005 by Ace Jones
+ email : acejones@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstring.h>
+#include <qdom.h>
+
+// ----------------------------------------------------------------------------
+// KDE Includes
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "mymoneyfile.h"
+#include "mymoneyreport.h"
+
+const QStringList MyMoneyReport::kRowTypeText = QStringList::split ( ",", "none,assetliability,expenseincome,category,topcategory,account,payee,month,week,topaccount,topaccount-account,equitytype,accounttype,institution,budget,budgetactual,schedule,accountinfo,accountloaninfo,accountreconcile,cashflow", true );
+const QStringList MyMoneyReport::kColumnTypeText = QStringList::split ( ",", "none,months,bimonths,quarters,4,5,6,weeks,8,9,10,11,years", true );
+
+// if you add names here, don't forget to update the bitmap for EQueryColumns
+// and shift the bit for eQCend one position to the left
+const QStringList MyMoneyReport::kQueryColumnsText = QStringList::split ( ",", "none,number,payee,category,memo,account,reconcileflag,action,shares,price,performance,loan,balance", true );
+
+const MyMoneyReport::EReportType MyMoneyReport::kTypeArray[] = { eNoReport, ePivotTable, ePivotTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, ePivotTable, ePivotTable, eInfoTable, eInfoTable, eInfoTable, eQueryTable, eQueryTable, eNoReport };
+const QStringList MyMoneyReport::kDetailLevelText = QStringList::split ( ",", "none,all,top,group,total,invalid", true );
+const QStringList MyMoneyReport::kChartTypeText = QStringList::split ( ",", "none,line,bar,pie,ring,stackedbar,invalid", true );
+
+// This should live in mymoney/mymoneytransactionfilter.h
+static const QStringList kTypeText = QStringList::split ( ",", "all,payments,deposits,transfers,none" );
+static const QStringList kStateText = QStringList::split ( ",", "all,notreconciled,cleared,reconciled,frozen,none" );
+static const QStringList kDateLockText = QStringList::split ( ",", "alldates,untiltoday,currentmonth,currentyear,monthtodate,yeartodate,yeartomonth,lastmonth,lastyear,last7days,last30days,last3months,last6months,last12months,next7days,next30days,next3months,next6months,next12months,userdefined,last3tonext3months,last11Months,currentQuarter,lastQuarter,nextQuarter,currentFiscalYear,lastFiscalYear,today" );
+static const QStringList kAccountTypeText = QStringList::split ( ",", "unknown,checkings,savings,cash,creditcard,loan,certificatedep,investment,moneymarket,asset,liability,currency,income,expense,assetloan,stock,equity,invalid" );
+
+MyMoneyReport::MyMoneyReport() :
+ m_name ( "Unconfigured Pivot Table Report" ),
+ m_detailLevel ( eDetailNone ),
+ m_convertCurrency ( true ),
+ m_favorite ( false ),
+ m_tax ( false ),
+ m_investments ( false ),
+ m_loans ( false ),
+ m_reportType ( kTypeArray[eExpenseIncome] ),
+ m_rowType ( eExpenseIncome ),
+ m_columnType ( eMonths ),
+ m_columnsAreDays ( false ),
+ m_queryColumns ( eQCnone ),
+ m_dateLock ( userDefined ),
+ m_accountGroupFilter ( false ),
+ m_chartType ( eChartLine ),
+ m_chartDataLabels ( true ),
+ m_chartGridLines ( true ),
+ m_chartByDefault ( false ),
+ m_chartLineWidth ( 2 ),
+ m_includeSchedules ( false ),
+ m_includeTransfers ( false ),
+ m_includeBudgetActuals ( false ),
+ m_includeUnusedAccounts ( false ),
+ m_showRowTotals ( false ),
+ m_includeForecast ( false ),
+ m_includeMovingAverage ( false ),
+ m_includePrice ( false ),
+ m_includeAveragePrice ( false )
+{
+}
+
+MyMoneyReport::MyMoneyReport ( const QString& id, const MyMoneyReport& right ) :
+ MyMoneyObject ( id )
+{
+ *this = right;
+ setId ( id );
+}
+
+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_dateLock ( _dl ),
+ m_accountGroupFilter ( false ),
+ m_chartType ( eChartLine ),
+ m_chartDataLabels ( true ),
+ m_chartGridLines ( true ),
+ m_chartByDefault ( false ),
+ m_chartLineWidth ( 2 ),
+ m_includeSchedules ( false ),
+ m_includeTransfers ( false ),
+ m_includeBudgetActuals ( false ),
+ m_includeUnusedAccounts ( false ),
+ m_showRowTotals ( false ),
+ m_includeForecast ( false ),
+ m_includeMovingAverage ( false ),
+ m_includePrice ( false ),
+ m_includeAveragePrice ( false )
+{
+ if ( m_reportType == ePivotTable )
+ m_columnType = static_cast<EColumnType> ( _ct );
+ if ( m_reportType == eQueryTable )
+ m_queryColumns = static_cast<EQueryColumns> ( _ct );
+ setDateFilter ( _dl );
+
+ if ( ( _rt > static_cast<ERowType> ( sizeof ( kTypeArray ) / sizeof ( kTypeArray[0] ) ) )
+ || ( m_reportType == eNoReport ) )
+ throw new MYMONEYEXCEPTION ( "Invalid report type" );
+
+ 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 -- asoliverez@gmail.com
+ 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 );
+ }
+}
+
+MyMoneyReport::MyMoneyReport ( const QDomElement& node ) :
+ MyMoneyObject ( node )
+{
+ if ( !read ( node ) )
+ clearId();
+}
+
+void MyMoneyReport::clear ( void )
+{
+ m_accountGroupFilter = false;
+ m_accountGroups.clear();
+
+ MyMoneyTransactionFilter::clear();
+}
+
+void MyMoneyReport::validDateRange ( QDate& _db, QDate& _de )
+{
+ _db = fromDate();
+ _de = toDate();
+
+ // if either begin or end date are invalid we have one of the following
+ // possible date filters:
+ //
+ // a) begin date not set - first transaction until given end date
+ // b) end date not set - from given date until last transaction
+ // c) both not set - first transaction until last transaction
+ //
+ // If there is no transaction in the engine at all, we use the current
+ // year as the filter criteria.
+
+ if ( !_db.isValid() || !_de.isValid() ) {
+ QValueList<MyMoneyTransaction> list = MyMoneyFile::instance()->transactionList ( *this );
+ QDate tmpBegin, tmpEnd;
+
+ if ( !list.isEmpty() ) {
+ qHeapSort ( list );
+ tmpBegin = list.front().postDate();
+ tmpEnd = list.back().postDate();
+ } else {
+ tmpBegin = QDate ( QDate::currentDate().year(), 1, 1 ); // the first date in the file
+ tmpEnd = QDate ( QDate::currentDate().year(), 12, 31 );// the last date in the file
+ }
+ if ( !_db.isValid() )
+ _db = tmpBegin;
+ if ( !_de.isValid() )
+ _de = tmpEnd;
+ }
+ if ( _db > _de )
+ _db = _de;
+}
+
+void MyMoneyReport::setRowType ( ERowType _rt )
+{
+ m_rowType = _rt;
+ m_reportType = kTypeArray[_rt];
+
+ m_accountGroupFilter = false;
+ m_accountGroups.clear();
+
+ if ( _rt == MyMoneyReport::eAssetLiability )
+ {
+ addAccountGroup ( MyMoneyAccount::Asset );
+ addAccountGroup ( MyMoneyAccount::Liability );
+ }
+ if ( _rt == MyMoneyReport::eExpenseIncome )
+ {
+ addAccountGroup ( MyMoneyAccount::Expense );
+ addAccountGroup ( MyMoneyAccount::Income );
+ }
+}
+
+bool MyMoneyReport::accountGroups(QValueList<MyMoneyAccount::accountTypeE>& list) const
+
+{
+ bool result = m_accountGroupFilter;
+
+ if ( result )
+ {
+ QValueList<MyMoneyAccount::accountTypeE>::const_iterator it_group = m_accountGroups.begin();
+ while ( it_group != m_accountGroups.end() )
+ {
+ list += ( *it_group );
+ ++it_group;
+ }
+ }
+ return result;
+}
+
+void MyMoneyReport::addAccountGroup ( MyMoneyAccount::accountTypeE type )
+{
+ if ( !m_accountGroups.isEmpty() && type != MyMoneyAccount::UnknownAccountType ) {
+ if ( m_accountGroups.contains ( type ) )
+ return;
+ }
+ m_accountGroupFilter = true;
+ if ( type != MyMoneyAccount::UnknownAccountType )
+ m_accountGroups.push_back ( type );
+}
+
+bool MyMoneyReport::includesAccountGroup( MyMoneyAccount::accountTypeE type ) const
+{
+ bool result = ( ! m_accountGroupFilter )
+ || ( isIncludingTransfers() && m_rowType == MyMoneyReport::eExpenseIncome )
+ || m_accountGroups.contains ( type );
+
+ return result;
+}
+
+bool MyMoneyReport::includes( const MyMoneyAccount& acc ) const
+{
+ bool result = false;
+
+ if ( includesAccountGroup ( acc.accountGroup() ) )
+ {
+ switch ( acc.accountGroup() )
+ {
+ case MyMoneyAccount::Income:
+ case MyMoneyAccount::Expense:
+ if ( isTax() )
+ result = ( acc.value ( "Tax" ) == "Yes" ) && includesCategory ( acc.id() );
+ else
+ result = includesCategory ( acc.id() );
+ break;
+ case MyMoneyAccount::Asset:
+ case MyMoneyAccount::Liability:
+ if ( isLoansOnly() )
+ result = acc.isLoan() && includesAccount ( acc.id() );
+ else if ( isInvestmentsOnly() )
+ result = acc.isInvest() && includesAccount ( acc.id() );
+ else if ( isIncludingTransfers() && m_rowType == MyMoneyReport::eExpenseIncome )
+ // If transfers are included, ONLY include this account if it is NOT
+ // included in the report itself!!
+ result = ! includesAccount ( acc.id() );
+ else
+ result = includesAccount ( acc.id() );
+ break;
+ default:
+ result = includesAccount ( acc.id() );
+ }
+ }
+ return result;
+}
+
+void MyMoneyReport::write ( QDomElement& e, QDomDocument *doc, bool anonymous ) const
+{
+ // No matter what changes, be sure to have a 'type' attribute. Only change
+ // the major type if it becomes impossible to maintain compatability with
+ // older versions of the program as new features are added to the reports.
+ // Feel free to change the minor type every time a change is made here.
+
+ writeBaseXML ( *doc, e );
+
+ if ( anonymous )
+ {
+ e.setAttribute ( "name", m_id );
+ e.setAttribute ( "comment", QString ( m_comment ).fill ( 'x' ) );
+ }
+ else
+ {
+ e.setAttribute ( "name", m_name );
+ e.setAttribute ( "comment", m_comment );
+ }
+ e.setAttribute ( "group", m_group );
+ e.setAttribute ( "convertcurrency", m_convertCurrency );
+ e.setAttribute ( "favorite", m_favorite );
+ e.setAttribute ( "tax", m_tax );
+ e.setAttribute ( "investments", m_investments );
+ e.setAttribute ( "loans", m_loans );
+ e.setAttribute ( "rowtype", kRowTypeText[m_rowType] );
+ e.setAttribute ( "datelock", kDateLockText[m_dateLock] );
+ e.setAttribute ( "includeschedules", m_includeSchedules );
+ e.setAttribute ( "columnsaredays", m_columnsAreDays );
+ e.setAttribute ( "includestransfers", m_includeTransfers );
+ if ( !m_budgetId.isEmpty() )
+ e.setAttribute ( "budget", m_budgetId );
+ e.setAttribute ( "includesactuals", m_includeBudgetActuals );
+ e.setAttribute ( "includeunused", m_includeUnusedAccounts );
+ e.setAttribute ( "includesforecast", m_includeForecast );
+ e.setAttribute ( "includesprice", m_includePrice );
+ e.setAttribute ( "includesaverageprice", m_includeAveragePrice );
+ e.setAttribute ( "includesmovingaverage", m_includeMovingAverage );
+ if( m_includeMovingAverage )
+ e.setAttribute ( "movingaveragedays", m_movingAverageDays );
+
+ e.setAttribute ( "charttype", kChartTypeText[m_chartType] );
+ e.setAttribute ( "chartdatalabels", m_chartDataLabels );
+ e.setAttribute ( "chartgridlines", m_chartGridLines );
+ e.setAttribute ( "chartbydefault", m_chartByDefault );
+ e.setAttribute ( "chartlinewidth", m_chartLineWidth );
+
+ if ( m_reportType == ePivotTable )
+ {
+ e.setAttribute ( "type", "pivottable 1.15" );
+ e.setAttribute ( "detail", kDetailLevelText[m_detailLevel] );
+ e.setAttribute ( "columntype", kColumnTypeText[m_columnType] );
+ e.setAttribute ( "showrowtotals", m_showRowTotals );
+ }
+ else if ( m_reportType == eQueryTable )
+ {
+ e.setAttribute ( "type", "querytable 1.14" );
+
+ QStringList columns;
+ unsigned qc = m_queryColumns;
+ unsigned it_qc = eQCbegin;
+ unsigned index = 1;
+ while ( it_qc != eQCend )
+ {
+ if ( qc & it_qc )
+ columns += kQueryColumnsText[index];
+ it_qc *= 2;
+ index++;
+ }
+ e.setAttribute ( "querycolumns", columns.join ( "," ) );
+ }
+ else if ( m_reportType == eInfoTable )
+ {
+ e.setAttribute ( "type", "infotable 1.0" );
+ e.setAttribute ( "detail", kDetailLevelText[m_detailLevel] );
+ e.setAttribute ( "showrowtotals", m_showRowTotals );
+ }
+
+ //
+ // Text Filter
+ //
+
+ QRegExp textfilter;
+ if ( textFilter ( textfilter ) )
+ {
+ QDomElement f = doc->createElement ( "TEXT" );
+ f.setAttribute ( "pattern", textfilter.pattern() );
+ f.setAttribute ( "casesensitive", textfilter.caseSensitive() );
+ f.setAttribute ( "regex", !textfilter.wildcard() );
+ f.setAttribute ( "inverttext", m_invertText );
+ e.appendChild ( f );
+ }
+
+ //
+ // Type & State Filters
+ //
+ QValueList<int> typelist;
+ if ( types ( typelist ) && ! typelist.empty() )
+ {
+ // iterate over payees, and add each one
+ QValueList<int>::const_iterator it_type = typelist.begin();
+ while ( it_type != typelist.end() )
+ {
+ QDomElement p = doc->createElement ( "TYPE" );
+ p.setAttribute ( "type", kTypeText[*it_type] );
+ e.appendChild ( p );
+
+ ++it_type;
+ }
+ }
+
+ QValueList<int> statelist;
+ if ( states ( statelist ) && ! statelist.empty() )
+ {
+ // iterate over payees, and add each one
+ QValueList<int>::const_iterator it_state = statelist.begin();
+ while ( it_state != statelist.end() )
+ {
+ QDomElement p = doc->createElement ( "STATE" );
+ p.setAttribute ( "state", kStateText[*it_state] );
+ e.appendChild ( p );
+
+ ++it_state;
+ }
+ }
+ //
+ // Number Filter
+ //
+
+ QString nrFrom, nrTo;
+ if ( numberFilter ( nrFrom, nrTo ) )
+ {
+ QDomElement f = doc->createElement ( "NUMBER" );
+ f.setAttribute ( "from", nrFrom );
+ f.setAttribute ( "to", nrTo );
+ e.appendChild ( f );
+ }
+
+ //
+ // Amount Filter
+ //
+
+ MyMoneyMoney from, to;
+ if ( amountFilter ( from, to ) ) // bool getAmountFilter(MyMoneyMoney&,MyMoneyMoney&);
+ {
+ QDomElement f = doc->createElement ( "AMOUNT" );
+ f.setAttribute ( "from", from.toString() );
+ f.setAttribute ( "to", to.toString() );
+ e.appendChild ( f );
+ }
+
+ //
+ // Payees Filter
+ //
+
+ QStringList payeelist;
+ if ( payees ( payeelist ) )
+ {
+ if ( payeelist.empty() )
+ {
+ QDomElement p = doc->createElement ( "PAYEE" );
+ e.appendChild ( p );
+ }
+ else
+ {
+ // iterate over payees, and add each one
+ QStringList::const_iterator it_payee = payeelist.begin();
+ while ( it_payee != payeelist.end() )
+ {
+ QDomElement p = doc->createElement ( "PAYEE" );
+ p.setAttribute ( "id", *it_payee );
+ e.appendChild ( p );
+
+ ++it_payee;
+ }
+ }
+ }
+
+ //
+ // Account Groups Filter
+ //
+
+ QValueList<MyMoneyAccount::accountTypeE> accountgrouplist;
+ if ( accountGroups ( accountgrouplist ) )
+ {
+ // iterate over accounts, and add each one
+ QValueList<MyMoneyAccount::accountTypeE>::const_iterator it_group = accountgrouplist.begin();
+ while ( it_group != accountgrouplist.end() )
+ {
+ QDomElement p = doc->createElement ( "ACCOUNTGROUP" );
+ p.setAttribute ( "group", kAccountTypeText[*it_group] );
+ e.appendChild ( p );
+
+ ++it_group;
+ }
+ }
+
+ //
+ // Accounts Filter
+ //
+
+ QStringList accountlist;
+ if ( accounts ( accountlist ) )
+ {
+ // iterate over accounts, and add each one
+ QStringList::const_iterator it_account = accountlist.begin();
+ while ( it_account != accountlist.end() )
+ {
+ QDomElement p = doc->createElement ( "ACCOUNT" );
+ p.setAttribute ( "id", *it_account );
+ e.appendChild ( p );
+
+ ++it_account;
+ }
+ }
+
+ //
+ // Categories Filter
+ //
+
+ accountlist.clear();
+ if ( categories ( accountlist ) )
+ {
+ // iterate over accounts, and add each one
+ QStringList::const_iterator it_account = accountlist.begin();
+ while ( it_account != accountlist.end() )
+ {
+ QDomElement p = doc->createElement ( "CATEGORY" );
+ p.setAttribute ( "id", *it_account );
+ e.appendChild ( p );
+
+ ++it_account;
+ }
+ }
+
+ //
+ // Date Filter
+ //
+
+ if ( m_dateLock == userDefined )
+ {
+ QDate dateFrom, dateTo;
+ if ( dateFilter ( dateFrom, dateTo ) )
+ {
+ QDomElement f = doc->createElement ( "DATES" );
+ if ( dateFrom.isValid() )
+ f.setAttribute ( "from", dateFrom.toString ( Qt::ISODate ) );
+ if ( dateTo.isValid() )
+ f.setAttribute ( "to", dateTo.toString ( Qt::ISODate ) );
+ e.appendChild ( f );
+ }
+ }
+}
+
+bool MyMoneyReport::read ( const QDomElement& e )
+{
+ // The goal of this reading method is 100% backward AND 100% forward
+ // compatability. Any report ever created with any version of KMyMoney
+ // should be able to be loaded by this method (as long as it's one of the
+ // report types supported in this version, of course)
+
+ bool result = false;
+
+ if (
+ "REPORT" == e.tagName()
+ &&
+ (
+ ( e.attribute ( "type" ).find ( "pivottable 1." ) == 0 )
+ ||
+ ( e.attribute ( "type" ).find ( "querytable 1." ) == 0 )
+ ||
+ ( e.attribute ( "type" ).find ( "infotable 1." ) == 0 )
+ )
+ )
+ {
+ result = true;
+ clear();
+
+ int i;
+ m_name = e.attribute ( "name" );
+ m_comment = e.attribute ( "comment", "Extremely old report" );
+
+ //set report type
+ if(!e.attribute ( "type" ).find ( "pivottable" )) {
+ m_reportType = MyMoneyReport::ePivotTable;
+ } else if(!e.attribute ( "type" ).find ( "querytable" )) {
+ m_reportType = MyMoneyReport::eQueryTable;
+ } else if(!e.attribute ( "type" ).find ( "infotable" )) {
+ m_reportType = MyMoneyReport::eInfoTable;
+ } else {
+ m_reportType = MyMoneyReport::eNoReport;
+ }
+
+ // Removed the line that screened out loading reports that are called
+ // "Default Report". It's possible for the user to change the comment
+ // to this, and we'd hate for it to break as a result.
+ m_group = e.attribute ( "group" );
+ m_id = e.attribute ( "id" );
+
+ //check for reports with older settings which didn't have the detail attribute
+ if ( e.hasAttribute ( "detail" ) )
+ {
+ i = kDetailLevelText.findIndex ( e.attribute ( "detail", "all" ) );
+ if ( i != -1 )
+ m_detailLevel = static_cast<EDetailLevel> ( i );
+ } else if ( e.attribute ( "showsubaccounts", "0" ).toUInt() ) {
+ //set to show all accounts
+ m_detailLevel = eDetailAll;
+ } else {
+ //set to show the top level account instead
+ m_detailLevel = eDetailTop;
+ }
+
+ m_convertCurrency = e.attribute ( "convertcurrency", "1" ).toUInt();
+ m_favorite = e.attribute ( "favorite", "0" ).toUInt();
+ m_tax = e.attribute ( "tax", "0" ).toUInt();
+ m_investments = e.attribute ( "investments", "0" ).toUInt();
+ m_loans = e.attribute ( "loans", "0" ).toUInt();
+ m_includeSchedules = e.attribute ( "includeschedules", "0" ).toUInt();
+ m_columnsAreDays = e.attribute ( "columnsaredays", "0" ).toUInt();
+ m_includeTransfers = e.attribute ( "includestransfers", "0" ).toUInt();
+ if ( e.hasAttribute ( "budget" ) )
+ m_budgetId = e.attribute ( "budget" );
+ m_includeBudgetActuals = e.attribute ( "includesactuals", "0" ).toUInt();
+ m_includeUnusedAccounts = e.attribute ( "includeunused", "0" ).toUInt();
+ m_includeForecast = e.attribute ( "includesforecast", "0" ).toUInt();
+ m_includePrice = e.attribute ( "includesprice", "0" ).toUInt();
+ m_includeAveragePrice = e.attribute ( "includesaverageprice", "0" ).toUInt();
+ m_includeMovingAverage = e.attribute ( "includesmovingaverage", "0" ).toUInt();
+ if( m_includeMovingAverage )
+ m_movingAverageDays = e.attribute ( "movingaveragedays", "1" ).toUInt();
+
+ //only load chart data if it is a pivot table
+ if ( m_reportType == ePivotTable ) {
+ i = kChartTypeText.findIndex ( e.attribute ( "charttype" ) );
+
+ if ( i != -1 )
+ m_chartType = static_cast<EChartType> ( i );
+
+ //if it is invalid, set to first type
+ if (m_chartType == eChartEnd)
+ m_chartType = eChartLine;
+
+ m_chartDataLabels = e.attribute ( "chartdatalabels", "1" ).toUInt();
+ m_chartGridLines = e.attribute ( "chartgridlines", "1" ).toUInt();
+ m_chartByDefault = e.attribute ( "chartbydefault", "0" ).toUInt();
+ m_chartLineWidth = e.attribute ( "chartlinewidth", "2" ).toUInt();
+ } else {
+ m_chartType = static_cast<EChartType> ( 0 );
+ m_chartDataLabels = true;
+ m_chartGridLines = true;
+ m_chartByDefault = false;
+ m_chartLineWidth = 1;
+ }
+
+ QString datelockstr = e.attribute ( "datelock", "userdefined" );
+ // Handle the pivot 1.2/query 1.1 case where the values were saved as
+ // numbers
+ bool ok = false;
+ i = datelockstr.toUInt ( &ok );
+ if ( !ok )
+ {
+ i = kDateLockText.findIndex ( datelockstr );
+ if ( i == -1 )
+ i = userDefined;
+ }
+ setDateFilter ( static_cast<dateOptionE> ( i ) );
+
+ i = kRowTypeText.findIndex ( e.attribute ( "rowtype", "expenseincome" ) );
+ if ( i != -1 )
+ {
+ setRowType ( static_cast<ERowType> ( i ) );
+ // recent versions of KMyMoney always showed a total column for
+ // income/expense reports. We turn it on for backward compatability
+ // here. If the total column is turned off, the flag will be reset
+ // in the next step
+ if ( i == eExpenseIncome )
+ m_showRowTotals = true;
+ }
+ if ( e.hasAttribute ( "showrowtotals" ) )
+ m_showRowTotals = e.attribute ( "showrowtotals" ).toUInt();
+
+ i = kColumnTypeText.findIndex ( e.attribute ( "columntype", "months" ) );
+ if ( i != -1 )
+ setColumnType ( static_cast<EColumnType> ( i ) );
+
+ unsigned qc = 0;
+ QStringList columns = QStringList::split ( ",", e.attribute ( "querycolumns", "none" ) );
+ QStringList::const_iterator it_column = columns.begin();
+ while ( it_column != columns.end() )
+ {
+ i = kQueryColumnsText.findIndex ( *it_column );
+ if ( i > 0 )
+ qc |= ( 1 << ( i - 1 ) );
+
+ ++it_column;
+ }
+ setQueryColumns ( static_cast<EQueryColumns> ( qc ) );
+
+ QDomNode child = e.firstChild();
+ while ( !child.isNull() && child.isElement() )
+ {
+ QDomElement c = child.toElement();
+ if ( "TEXT" == c.tagName() && c.hasAttribute ( "pattern" ) )
+ {
+ setTextFilter ( QRegExp ( c.attribute ( "pattern" ), c.attribute ( "casesensitive", "1" ).toUInt(), !c.attribute ( "regex", "1" ).toUInt() ), c.attribute ( "inverttext", "0" ).toUInt() );
+ }
+ if ( "TYPE" == c.tagName() && c.hasAttribute ( "type" ) )
+ {
+ i = kTypeText.findIndex ( c.attribute ( "type" ) );
+ if ( i != -1 )
+ addType ( i );
+ }
+ if ( "STATE" == c.tagName() && c.hasAttribute ( "state" ) )
+ {
+ i = kStateText.findIndex ( c.attribute ( "state" ) );
+ if ( i != -1 )
+ addState ( i );
+ }
+ if ( "NUMBER" == c.tagName() )
+ {
+ setNumberFilter ( c.attribute ( "from" ), c.attribute ( "to" ) );
+ }
+ if ( "AMOUNT" == c.tagName() )
+ {
+ setAmountFilter ( MyMoneyMoney ( c.attribute ( "from", "0/100" ) ), MyMoneyMoney ( c.attribute ( "to", "0/100" ) ) );
+ }
+ if ( "DATES" == c.tagName() )
+ {
+ QDate from, to;
+ if ( c.hasAttribute ( "from" ) )
+ from = QDate::fromString ( c.attribute ( "from" ), Qt::ISODate );
+ if ( c.hasAttribute ( "to" ) )
+ to = QDate::fromString ( c.attribute ( "to" ), Qt::ISODate );
+ MyMoneyTransactionFilter::setDateFilter ( from, to );
+ }
+ if ( "PAYEE" == c.tagName() )
+ {
+ addPayee ( c.attribute ( "id" ) );
+ }
+ if ( "CATEGORY" == c.tagName() && c.hasAttribute ( "id" ) )
+ {
+ addCategory ( c.attribute ( "id" ) );
+ }
+ if ( "ACCOUNT" == c.tagName() && c.hasAttribute ( "id" ) )
+ {
+ addAccount ( c.attribute ( "id" ) );
+ }
+ if ( "ACCOUNTGROUP" == c.tagName() && c.hasAttribute ( "group" ) )
+ {
+ i = kAccountTypeText.findIndex ( c.attribute ( "group" ) );
+ if ( i != -1 )
+ addAccountGroup ( static_cast<MyMoneyAccount::accountTypeE> ( i ) );
+ }
+ child = child.nextSibling();
+ }
+ }
+ return result;
+}
+
+void MyMoneyReport::writeXML ( QDomDocument& document, QDomElement& parent ) const
+{
+ QDomElement el = document.createElement ( "REPORT" );
+ write ( el, &document, false );
+ parent.appendChild ( el );
+}
+
+bool MyMoneyReport::hasReferenceTo ( const QString& id ) const
+{
+ QStringList list;
+
+ // collect all ids
+ accounts ( list );
+ categories ( list );
+ payees ( list );
+
+ return ( list.contains ( id ) > 0 );
+}
+
+// vim:cin:si:ai:et:ts=2:sw=2:
diff --git a/kmymoney2/mymoney/mymoneyreport.h b/kmymoney2/mymoney/mymoneyreport.h
new file mode 100644
index 0000000..e467179
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyreport.h
@@ -0,0 +1,497 @@
+/***************************************************************************
+ mymoneyreport.h
+ -------------------
+ begin : Sun July 4 2004
+ copyright : (C) 2004-2005 by Ace Jones
+ email : acejones@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYREPORT_H
+#define MYMONEYREPORT_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+#include <qmap.h>
+#include <qvaluelist.h>
+#include <qstring.h>
+class QDomElement;
+class QDomDocument;
+
+// ----------------------------------------------------------------------------
+// Project Includes
+#include <kmymoney/mymoneyobject.h>
+#include <kmymoney/mymoneyaccount.h>
+#include <kmymoney/mymoneytransactionfilter.h>
+#include <kmymoney/export.h>
+
+/**
+ * This class defines a report within the MyMoneyEngine. The report class
+ * contains all the configuration parameters needed to run a report, plus
+ * XML serialization.
+ *
+ * A report is a transactionfilter, so any report can specify which
+ * transactions it's interested down to the most minute level of detail.
+ * It extends the transactionfilter by providing identification (name,
+ * comments, group type, etc) as well as layout information (what kind
+ * of layout should be used, how the rows & columns should be presented,
+ * currency converted, etc.)
+ *
+ * As noted above, this class only provides a report DEFINITION. The
+ * generation and presentation of the report itself are left to higher
+ * level classes.
+ *
+ * @author Ace Jones <acejones@users.sourceforge.net>
+ */
+
+class KMYMONEY_EXPORT MyMoneyReport: public MyMoneyObject, public MyMoneyTransactionFilter
+{
+public:
+ // When adding a new row type, be sure to add a corresponding entry in kTypeArray
+ enum ERowType { eNoRows = 0, eAssetLiability, eExpenseIncome, eCategory, eTopCategory, eAccount, ePayee, eMonth, eWeek, eTopAccount, eAccountByTopAccount, eEquityType, eAccountType, eInstitution, eBudget, eBudgetActual, eSchedule, eAccountInfo, eAccountLoanInfo, eAccountReconcile, eCashFlow};
+ enum EReportType { eNoReport = 0, ePivotTable, eQueryTable, eInfoTable };
+ enum EColumnType { eNoColumns = 0, eDays = 1, eMonths = 1, eBiMonths = 2, eQuarters = 3, eWeeks = 7, eYears = 12 };
+
+ // if you add bits to this bitmask, start with the value currently assigned to eQCend and update its value afterwards
+ // also don't forget to add column names to kQueryColumnsText in mymoneyreport.cpp
+ enum EQueryColumns { eQCnone = 0x0, eQCbegin = 0x1, eQCnumber = 0x1, eQCpayee = 0x2, eQCcategory = 0x4, eQCmemo = 0x8, eQCaccount = 0x10, eQCreconciled = 0x20, eQCaction = 0x40, eQCshares = 0x80, eQCprice = 0x100, eQCperformance = 0x200, eQCloan = 0x400, eQCbalance = 0x800, eQCend = 0x1000 };
+
+ enum EDetailLevel { eDetailNone = 0, eDetailAll, eDetailTop, eDetailGroup, eDetailTotal, eDetailEnd };
+ enum EChartType { eChartNone = 0, eChartLine, eChartBar, eChartPie, eChartRing, eChartStackedBar, eChartEnd };
+
+ static const QStringList kRowTypeText;
+ static const QStringList kColumnTypeText;
+ static const QStringList kQueryColumnsText;
+ static const QStringList kDetailLevelText;
+ static const QStringList kChartTypeText;
+ static const EReportType kTypeArray[];
+
+public:
+ MyMoneyReport(void);
+ MyMoneyReport(ERowType _rt, unsigned _ct, dateOptionE _dl, EDetailLevel _ss, const QString& _name, const QString& _comment );
+ MyMoneyReport(const QString& id, const MyMoneyReport& right);
+
+ /**
+ * This constructor creates an object based on the data found in the
+ * QDomElement referenced by @p node. If problems arise, the @p id of
+ * the object is cleared (see MyMoneyObject::clearId()).
+ */
+ MyMoneyReport(const QDomElement& node);
+
+ // Simple get operations
+ const QString& name(void) const { return m_name; }
+ bool isShowingRowTotals(void) const { return (m_showRowTotals); }
+ EReportType reportType(void) const { return m_reportType; }
+ ERowType rowType(void) const { return m_rowType; }
+ EColumnType columnType(void) const { return m_columnType; }
+ bool isRunningSum(void) const { return (m_rowType==eAssetLiability); }
+ bool isConvertCurrency(void) const { return m_convertCurrency; }
+ unsigned columnPitch(void) const { return static_cast<unsigned>(m_columnType); }
+ bool isShowingColumnTotals(void) const { return m_convertCurrency; }
+ const QString& comment( void ) const { return m_comment; }
+ EQueryColumns queryColumns(void) const { return m_queryColumns; }
+ const QString& group( void ) const { return m_group; }
+ bool isFavorite(void) const { return m_favorite; }
+ bool isTax(void) const { return m_tax; }
+ bool isInvestmentsOnly(void) const { return m_investments; }
+ bool isLoansOnly(void) const { return m_loans; }
+ EDetailLevel detailLevel(void) const { return m_detailLevel; }
+ EChartType chartType(void) const { return m_chartType; }
+ bool isChartDataLabels(void) const { return m_chartDataLabels; }
+ bool isChartGridLines(void) const { return m_chartGridLines; }
+ bool isChartByDefault(void) const { return m_chartByDefault; }
+ uint chartLineWidth(void) const { return m_chartLineWidth; }
+ bool isIncludingSchedules(void) const { return m_includeSchedules; }
+ bool isColumnsAreDays(void) const { return m_columnsAreDays; }
+ bool isIncludingTransfers(void) const { return m_includeTransfers; }
+ bool isIncludingUnusedAccounts(void) const { return m_includeUnusedAccounts; }
+ bool hasBudget(void) const { return !m_budgetId.isEmpty(); }
+ const QString& budget(void) const { return m_budgetId; }
+ bool isIncludingBudgetActuals(void) const { return m_includeBudgetActuals; }
+ bool isIncludingForecast(void) const { return m_includeForecast; }
+ bool isIncludingMovingAverage(void) const { return m_includeMovingAverage; }
+ int movingAverageDays(void) const { return m_movingAverageDays; }
+ bool isIncludingPrice(void) const { return m_includePrice; }
+ bool isIncludingAveragePrice(void) const { return m_includeAveragePrice; }
+ bool isUserDefined(void) const { return m_dateLock == userDefined; }
+
+ // Simple set operations
+ void setName(const QString& _s) { m_name = _s; }
+ void setConvertCurrency(bool _f) { m_convertCurrency = _f; }
+ void setRowType(ERowType _rt);
+ void setColumnType(EColumnType _ct) { m_columnType = _ct; }
+ void setComment( const QString& _comment ) { m_comment = _comment; }
+ void setGroup( const QString& _group ) { m_group = _group; }
+ void setFavorite(bool _f) { m_favorite = _f; }
+ void setQueryColumns( EQueryColumns _qc ) { m_queryColumns = _qc; }
+ void setTax(bool _f) { m_tax = _f; }
+ void setInvestmentsOnly(bool _f) { m_investments = _f; if (_f) m_loans = false; }
+ void setLoansOnly(bool _f) { m_loans = _f; if (_f) m_investments = false; }
+ void setDetailLevel( EDetailLevel _detail ) { m_detailLevel = _detail; }
+ void setChartType ( EChartType _type ) { m_chartType = _type; }
+ void setChartDataLabels ( bool _f ) { m_chartDataLabels = _f; }
+ void setChartGridLines ( bool _f ) { m_chartGridLines = _f; }
+ void setChartByDefault ( bool _f ) { m_chartByDefault = _f; }
+ void setChartLineWidth ( uint _f ) { m_chartLineWidth = _f; }
+ void setIncludingSchedules( bool _f ) { m_includeSchedules = _f; }
+ void setColumnsAreDays( bool _f ) { m_columnsAreDays = _f; }
+ void setIncludingTransfers( bool _f ) { m_includeTransfers = _f; }
+ void setIncludingUnusedAccounts( bool _f ) { m_includeUnusedAccounts = _f; }
+ void setShowingRowTotals( bool _f ) { m_showRowTotals = _f; }
+ void setIncludingBudgetActuals( bool _f ) { m_includeBudgetActuals = _f; }
+ void setIncludingForecast( bool _f ) { m_includeForecast = _f; }
+ void setIncludingMovingAverage( bool _f ) { m_includeMovingAverage = _f; }
+ void setMovingAverageDays( int _days ) { m_movingAverageDays = _days; }
+ void setIncludingPrice( bool _f ) { m_includePrice = _f; }
+ void setIncludingAveragePrice( bool _f ) { m_includeAveragePrice = _f; }
+
+ /**
+ * Sets the budget used for this report
+ *
+ * @param _budget The ID of the budget to use, or an empty string
+ * to indicate a budget is NOT included
+ * @param _fa Whether to display actual data alongside the budget.
+ * Setting to false means the report displays ONLY the budget itself.
+ * @warning For now, the budget ID is ignored. The budget id is
+ * simply checked for any non-empty string, and if so, hasBudget()
+ * will return true.
+ */
+ void setBudget( const QString& _budget, bool _fa = true ) { m_budgetId = _budget; m_includeBudgetActuals=_fa; }
+
+ /**
+ * This method allows you to clear the underlying transaction filter
+ */
+ void clear(void);
+
+ /**
+ * This method allows you to set the underlying transaction filter
+ *
+ * @param _filter The filter which should replace the existing transaction
+ * filter.
+ */
+ void assignFilter(const MyMoneyTransactionFilter& _filter) { MyMoneyTransactionFilter::operator=(_filter); }
+
+ /**
+ * Set the underlying date filter and LOCK that filter to the specified
+ * range. For example, if @p _u is "CurrentMonth", this report should always
+ * be updated to the current month no matter when the report is run.
+ *
+ * This updating is not entirely automatic, you should update it yourself by
+ * calling updateDateFilter.
+ *
+ * @param _u The date range constant (MyMoneyTransactionFilter::dateRangeE)
+ * which this report should be locked to.
+ */
+
+ void setDateFilter(dateOptionE _u)
+ {
+ m_dateLock = _u;
+ if (_u != userDefined)
+ MyMoneyTransactionFilter::setDateFilter( _u );
+ }
+
+ /**
+ * Set the underlying date filter using the start and end dates provided.
+ * Note that this does not LOCK to any range like setDateFilter(unsigned)
+ * above. It is just a reimplementation of the MyMoneyTransactionFilter
+ * version.
+ *
+ * @param _db The inclusive begin date of the date range
+ * @param _de The inclusive end date of the date range
+ */
+
+ void setDateFilter(const QDate& _db,const QDate& _de) { MyMoneyTransactionFilter::setDateFilter( _db,_de ); }
+
+ /**
+ * Set the underlying date filter using the 'date lock' property.
+ *
+ * Always call this function before executing the report to be sure that
+ * the date filters properly match the plain-language 'date lock'.
+ *
+ * For example, if the report is date-locked to "Current Month", and the
+ * last time you loaded or ran the report was in August, but it's now
+ * September, this function will update the date range to be September,
+ * as is proper.
+ */
+ void updateDateFilter(void) { if (m_dateLock != userDefined) MyMoneyTransactionFilter::setDateFilter(m_dateLock); }
+
+ /**
+ * Retrieves a VALID beginning & ending date for this report.
+ *
+ * The underlying date filter can return en empty QDate() for either the
+ * begin or end date or both. This is typically unacceptable for reports,
+ * which need the REAL begin and end date.
+ *
+ * This function gets the underlying date filter range, and if either is
+ * an empty QDate(), it determines the missing date from looking at all
+ * the transactions which match the underlying filter, and returning the
+ * date of the first or last transaction (as appropriate).
+ *
+ * @param _db The inclusive begin date of the date range
+ * @param _de The inclusive end date of the date range
+ */
+ void validDateRange(QDate& _db, QDate& _de);
+
+ /**
+ * This method turns on the account group filter and adds the
+ * @p type to the list of allowed groups.
+ *
+ * Note that account group filtering is handled differently
+ * than all the filters of the underlying class. This filter
+ * is meant to be applied to individual splits of matched
+ * transactions AFTER the underlying filter is used to find
+ * the matching transactions.
+ *
+ * @param type the account group to add to the allowed groups list
+ */
+ void addAccountGroup(MyMoneyAccount::accountTypeE type);
+
+ /**
+ * This method returns whether an account group filter has been set,
+ * and if so, it returns all the account groups set in the filter.
+ *
+ * @param list list to append account groups into
+ * @return return true if an account group filter has been set
+ */
+ bool accountGroups(QValueList<MyMoneyAccount::accountTypeE>& list) const;
+
+ /**
+ * This method returns whether the specified account group
+ * is allowed by the account groups filter.
+ *
+ * @param type group to append account groups into
+ * @return return true if an account group filter has been set
+ */
+ bool includesAccountGroup( MyMoneyAccount::accountTypeE type ) const;
+
+ /**
+ * This method is used to test whether a specific account
+ * passes the accountGroup test and either the Account or
+ * Category test, depending on which sort of Account it is.
+ *
+ * The m_tax and m_investments properties are also considered.
+ *
+ * @param acc the account in question
+ * @return true if account is in filter set, false otherwise
+ */
+ bool includes( const MyMoneyAccount& acc ) const;
+
+ /**
+ * This method writes this report to the DOM element @p e,
+ * within the DOM document @p doc.
+ *
+ * @param e The element which should be populated with info from this report
+ * @param doc The document which we can use to create new sub-elements
+ * if needed
+ * @param anonymous Whether the sensitive parts of the report should be
+ * masked
+ */
+ void write(QDomElement& e, QDomDocument *doc, bool anonymous=false) const;
+
+ /**
+ * This method reads a report from the DOM element @p e, and
+ * populates this report with the results.
+ *
+ * @param e The element from which the report should be read
+ *
+ * @return bool True if a report was successfully loaded from the
+ * element @p e. If false is returned, the contents of this report
+ * object are undefined.
+ */
+ bool read(const QDomElement& e);
+
+ /**
+ * This method creates a QDomElement for the @p document
+ * under the parent node @p parent. (This version overwrites the
+ * MMObject base class.)
+ *
+ * @param document reference to QDomDocument
+ * @param parent reference to QDomElement parent node
+ */
+ virtual void writeXML(QDomDocument& document, QDomElement& parent) const;
+
+ /**
+ * This method checks if a reference to the given object exists. It returns,
+ * a @p true if the object is referencing the one requested by the
+ * parameter @p id. If it does not, this method returns @p false.
+ *
+ * @param id id of the object to be checked for references
+ * @retval true This object references object with id @p id.
+ * @retval false This object does not reference the object with id @p id.
+ */
+ virtual bool hasReferenceTo(const QString& id) const;
+
+private:
+ /**
+ * The user-assigned name of the report
+ */
+ QString m_name;
+ /**
+ * The user-assigned comment for the report, in case they want to make
+ * additional notes for themselves about the report.
+ */
+ QString m_comment;
+ /**
+ * Where to group this report amongst the others in the UI view. This
+ * should be assigned by the UI system.
+ */
+ QString m_group;
+ /**
+ * How much detail to show in the accounts
+ */
+ enum EDetailLevel m_detailLevel;
+ /**
+ * Whether to convert all currencies to the base currency of the file (true).
+ * If this is false, it's up to the report generator to decide how to handle
+ * the currency.
+ */
+ bool m_convertCurrency;
+ /**
+ * Whether this is one of the users' favorite reports
+ */
+ bool m_favorite;
+ /**
+ * Whether this report should only include categories marked as "Tax"="Yes"
+ */
+ bool m_tax;
+ /**
+ * Whether this report should only include investment accounts
+ */
+ bool m_investments;
+ /**
+ * Whether this report should only include loan accounts
+ * Applies only to querytable reports. Mutually exclusive with
+ * m_investments.
+ */
+ bool m_loans;
+ /**
+ * What sort of algorithm should be used to run the report
+ */
+ enum EReportType m_reportType;
+ /**
+ * What sort of values should show up on the ROWS of this report
+ */
+ enum ERowType m_rowType;
+ /**
+ * What sort of values should show up on the COLUMNS of this report,
+ * in the case of a 'PivotTable' report. Really this is used more as a
+ * QUANTITY of months or days. Whether it's months or days is determiend
+ * by m_columnsAreDays.
+ */
+ enum EColumnType m_columnType;
+ /**
+ * Whether the base unit of columns of this report is days. Only applies to
+ * 'PivotTable' reports. If false, then columns are months or multiples thereof.
+ */
+ bool m_columnsAreDays;
+ /**
+ * What sort of values should show up on the COLUMNS of this report,
+ * in the case of a 'QueryTable' report
+ */
+ enum EQueryColumns m_queryColumns;
+
+ /**
+ * The plain-language description of what the date range should be locked
+ * to. 'userDefined' means NO locking, in any other case, the report
+ * will be adjusted to match the date lock. So if the date lock is
+ * 'currentMonth', the start and end dates of the underlying filter will
+ * be updated to whatever the current month is. This updating happens
+ * automatically when the report is loaded, and should also be done
+ * manually by calling updateDateFilter() before generating the report
+ */
+ dateOptionE m_dateLock;
+ /**
+ * Which account groups should be included in the report. This filter
+ * is applied to the individual splits AFTER a transaction has been
+ * matched using the underlying filter.
+ */
+ QValueList<MyMoneyAccount::accountTypeE> m_accountGroups;
+ /**
+ * Whether an account group filter has been set (see m_accountGroups)
+ */
+ bool m_accountGroupFilter;
+ /**
+ * What format should be used to draw this report as a chart
+ */
+ enum EChartType m_chartType;
+ /**
+ * Whether the value of individual data points should be drawn on the chart
+ */
+ bool m_chartDataLabels;
+ /**
+ * Whether grid lines should be drawn on the chart
+ */
+ bool m_chartGridLines;
+ /**
+ * Whether this report should be shown as a chart by default (otherwise it
+ * should be shown as a textual report)
+ */
+ bool m_chartByDefault;
+ /**
+ * Width of the chart lines
+ */
+ uint m_chartLineWidth;
+ /**
+ * Whether to include scheduled transactions
+ */
+ bool m_includeSchedules;
+ /**
+ * Whether to include transfers. Only applies to Income/Expense reports
+ */
+ bool m_includeTransfers;
+ /**
+ * The id of the budget associated with this report.
+ */
+ QString m_budgetId;
+ /**
+ * Whether this report should print the actual data to go along with
+ * the budget. This is only valid if the report has a budget.
+ */
+ bool m_includeBudgetActuals;
+ /**
+ * Whether this report should include all accounts and not only
+ * accounts with transactions.
+ */
+ bool m_includeUnusedAccounts;
+ /**
+ * Whether this report should include columns for row totals
+ */
+ bool m_showRowTotals;
+ /**
+ * Whether this report should include forecast balance
+ */
+ bool m_includeForecast;
+ /**
+ * Whether this report should include moving average
+ */
+ bool m_includeMovingAverage;
+ /**
+ * The amount of days that spans each moving average
+ */
+ int m_movingAverageDays;
+ /**
+ * Whether this report should include prices
+ */
+ bool m_includePrice;
+ /**
+ * Whether this report should include moving average prices
+ */
+ bool m_includeAveragePrice;
+
+
+
+};
+
+#endif // MYMONEYREPORT_H
diff --git a/kmymoney2/mymoney/mymoneyscheduled.cpp b/kmymoney2/mymoney/mymoneyscheduled.cpp
new file mode 100644
index 0000000..88b4c7b
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyscheduled.cpp
@@ -0,0 +1,1372 @@
+/***************************************************************************
+ mymoneyscheduled.cpp
+ -------------------
+ copyright : (C) 2000-2002 by Michael Edwardes
+ (C) 2007 by Thomas Baumgart
+ email : mte@users.sourceforge.net
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+// ----------------------------------------------------------------------------
+// QT Includes
+
+// ----------------------------------------------------------------------------
+// KDE Includes
+
+#include <klocale.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "mymoneyscheduled.h"
+#include "mymoneyexception.h"
+#include "mymoneyfile.h"
+
+MyMoneySchedule::MyMoneySchedule() :
+ MyMoneyObject()
+{
+ // Set up the default values
+ m_occurence = OCCUR_ANY;
+ m_occurenceMultiplier = 1;
+ m_type = TYPE_ANY;
+ m_paymentType = STYPE_ANY;
+ m_fixed = false;
+ m_autoEnter = false;
+ m_startDate = QDate();
+ m_endDate = QDate();
+ m_lastPayment = QDate();
+ m_weekendOption = MoveNothing;
+}
+
+MyMoneySchedule::MyMoneySchedule(const QString& name, typeE type,
+ occurenceE occurence, int occurenceMultiplier,
+ paymentTypeE paymentType,
+ const QDate& /* startDate */,
+ const QDate& endDate,
+ bool fixed, bool autoEnter) :
+ MyMoneyObject()
+{
+ // Set up the default values
+ m_name = name;
+ m_occurence = occurence;
+ m_occurenceMultiplier = occurenceMultiplier;
+ simpleToCompoundOccurence(m_occurenceMultiplier,m_occurence);
+ m_type = type;
+ m_paymentType = paymentType;
+ m_fixed = fixed;
+ m_autoEnter = autoEnter;
+ m_startDate = QDate();
+ m_endDate = endDate;
+ m_lastPayment = QDate();
+ m_weekendOption = MoveNothing;
+}
+
+MyMoneySchedule::MyMoneySchedule(const QDomElement& node) :
+ MyMoneyObject(node)
+{
+ if("SCHEDULED_TX" != node.tagName())
+ throw new MYMONEYEXCEPTION("Node was not SCHEDULED_TX");
+
+ m_name = node.attribute("name");
+ m_startDate = stringToDate(node.attribute("startDate"));
+ m_endDate = stringToDate(node.attribute("endDate"));
+ m_lastPayment = stringToDate(node.attribute("lastPayment"));
+
+ m_type = static_cast<MyMoneySchedule::typeE>(node.attribute("type").toInt());
+ m_paymentType = static_cast<MyMoneySchedule::paymentTypeE>(node.attribute("paymentType").toInt());
+ m_occurence = static_cast<MyMoneySchedule::occurenceE>(node.attribute("occurence").toInt());
+ m_occurenceMultiplier = node.attribute("occurenceMultiplier", "1").toInt();
+ // Convert to compound occurence
+ simpleToCompoundOccurence(m_occurenceMultiplier,m_occurence);
+ m_autoEnter = static_cast<bool>(node.attribute("autoEnter").toInt());
+ m_fixed = static_cast<bool>(node.attribute("fixed").toInt());
+ m_weekendOption = static_cast<MyMoneySchedule::weekendOptionE>(node.attribute("weekendOption").toInt());
+
+ // read in the associated transaction
+ QDomNodeList nodeList = node.elementsByTagName("TRANSACTION");
+ if(nodeList.count() == 0)
+ throw new MYMONEYEXCEPTION("SCHEDULED_TX has no TRANSACTION node");
+
+ setTransaction(MyMoneyTransaction(nodeList.item(0).toElement(), false), true);
+
+ // some old versions did not remove the entry date and post date fields
+ // in the schedule. So if this is the case, we deal with a very old transaction
+ // and can't use the post date field as next due date. Hence, we wipe it out here
+ if(m_transaction.entryDate().isValid()) {
+ m_transaction.setPostDate(QDate());
+ m_transaction.setEntryDate(QDate());
+ }
+
+ // readin the recorded payments
+ nodeList = node.elementsByTagName("PAYMENTS");
+ if(nodeList.count() > 0) {
+ nodeList = nodeList.item(0).toElement().elementsByTagName("PAYMENT");
+ for(unsigned int i = 0; i < nodeList.count(); ++i) {
+ m_recordedPayments << stringToDate(nodeList.item(i).toElement().attribute("date"));
+ }
+ }
+
+ // if the next due date is not set (comes from old version)
+ // then set it up the old way
+ if(!nextDueDate().isValid() && !m_lastPayment.isValid()) {
+ m_transaction.setPostDate(m_startDate);
+ // clear it, because the schedule has never been used
+ m_startDate = QDate();
+ }
+
+ // There are reports that lastPayment and nextDueDate are identical or
+ // that nextDueDate is older than lastPayment. This could
+ // be caused by older versions of the application. In this case, we just
+ // clear out the nextDueDate and let it calculate from the lastPayment.
+ if(nextDueDate().isValid() && nextDueDate() <= m_lastPayment) {
+ m_transaction.setPostDate(QDate());
+ }
+
+ if(!nextDueDate().isValid()) {
+ m_transaction.setPostDate(m_startDate);
+ m_transaction.setPostDate(nextPayment(m_lastPayment.addDays(1)));
+ }
+}
+
+MyMoneySchedule::MyMoneySchedule(const QString& id, const MyMoneySchedule& right) :
+ MyMoneyObject(id)
+{
+ *this = right;
+ setId(id);
+}
+
+MyMoneySchedule::occurenceE MyMoneySchedule::occurence(void) const
+{
+ MyMoneySchedule::occurenceE occ = m_occurence;
+ int mult = m_occurenceMultiplier;
+ compoundToSimpleOccurence(mult, occ);
+ return occ;
+}
+
+void MyMoneySchedule::setStartDate(const QDate& date)
+{
+ m_startDate = date;
+}
+
+void MyMoneySchedule::setPaymentType(paymentTypeE type)
+{
+ m_paymentType = type;
+}
+
+void MyMoneySchedule::setFixed(bool fixed)
+{
+ m_fixed = fixed;
+}
+
+void MyMoneySchedule::setTransaction(const MyMoneyTransaction& transaction)
+{
+ setTransaction(transaction, false);
+}
+
+void MyMoneySchedule::setTransaction(const MyMoneyTransaction& transaction, bool noDateCheck)
+{
+ MyMoneyTransaction t = transaction;
+ if(!noDateCheck) {
+ // don't allow a transaction that has no due date
+ // if we get something like that, then we use the
+ // the current next due date. If that is also invalid
+ // we can't help it.
+ if(!t.postDate().isValid()) {
+ t.setPostDate(m_transaction.postDate());
+ }
+
+ if(!t.postDate().isValid())
+ return;
+ }
+
+ // make sure to clear out some unused information in scheduled transactions
+ // we need to do this for the case that the transaction passed as argument
+ // is a matched or imported transaction.
+ QValueList<MyMoneySplit> splits = t.splits();
+ if(splits.count() > 0) {
+ QValueList<MyMoneySplit>::const_iterator it_s;
+ for(it_s = splits.begin(); it_s != splits.end(); ++it_s) {
+ MyMoneySplit s = *it_s;
+ // clear out the bankID
+ if(!(*it_s).bankID().isEmpty()) {
+ s.setBankID(QString());
+ t.modifySplit(s);
+ }
+
+ // only clear payees from second split onwards
+ if(it_s == splits.begin())
+ continue;
+
+ if(!(*it_s).payeeId().isEmpty()) {
+ // but only if the split references an income/expense category
+ MyMoneyFile* file = MyMoneyFile::instance();
+ // some unit tests don't have a storage attached, so we
+ // simply skip the test
+ // Don't check for accounts with an id of 'Phony-ID' which is used
+ // internally for non-existing accounts (during creation of accounts)
+ if(file->storageAttached() && s.accountId() != QString("Phony-ID")) {
+ MyMoneyAccount acc = file->account(s.accountId());
+ if(acc.isIncomeExpense()) {
+ s.setPayeeId(QString());
+ t.modifySplit(s);
+ }
+ }
+ }
+ }
+ }
+
+ m_transaction = t;
+ // make sure that the transaction does not have an id so that we can enter
+ // it into the engine
+ m_transaction.clearId();
+}
+
+void MyMoneySchedule::setEndDate(const QDate& date)
+{
+ m_endDate = date;
+}
+
+void MyMoneySchedule::setAutoEnter(bool autoenter)
+{
+ m_autoEnter = autoenter;
+}
+
+const QDate& MyMoneySchedule::startDate(void) const
+{
+ if(m_startDate.isValid())
+ return m_startDate;
+ return nextDueDate();
+}
+
+const QDate& MyMoneySchedule::nextDueDate(void) const
+{
+ return m_transaction.postDate();
+}
+
+QDate MyMoneySchedule::adjustedNextDueDate(void) const
+{
+ if(isFinished())
+ return QDate();
+
+ return adjustedDate(nextDueDate(), weekendOption());
+}
+
+QDate MyMoneySchedule::adjustedDate(QDate date, weekendOptionE option) const
+{
+ if (option == MyMoneySchedule::MoveNothing)
+ return date;
+
+ int step = 1;
+ if (option == MyMoneySchedule::MoveFriday)
+ step = -1;
+
+ while (date.dayOfWeek() > 5)
+ date = date.addDays(step);
+
+ return date;
+}
+
+void MyMoneySchedule::setNextDueDate(const QDate& date)
+{
+ if(date.isValid()) {
+ m_transaction.setPostDate(date);
+ m_startDate = date;
+ }
+}
+
+void MyMoneySchedule::setLastPayment(const QDate& date)
+{
+ // Delete all payments older than date
+ QValueList<QDate>::Iterator it;
+ QValueList<QDate> delList;
+
+ for (it=m_recordedPayments.begin(); it!=m_recordedPayments.end(); ++it)
+ {
+ if (*it < date || !date.isValid())
+ delList.append(*it);
+ }
+
+ for (it=delList.begin(); it!=delList.end(); ++it)
+ {
+ m_recordedPayments.remove(*it);
+ }
+
+ m_lastPayment = date;
+ if(!m_startDate.isValid())
+ m_startDate = date;
+}
+
+void MyMoneySchedule::setName(const QString& nm)
+{
+ m_name = nm;
+}
+
+void MyMoneySchedule::setOccurence(occurenceE occ)
+{
+ MyMoneySchedule::occurenceE occ2 = occ;
+ int mult = 1;
+ simpleToCompoundOccurence(mult, occ2);
+ setOccurencePeriod( occ2 );
+ setOccurenceMultiplier( mult );
+}
+
+void MyMoneySchedule::setOccurencePeriod(occurenceE occ)
+{
+ m_occurence = occ;
+}
+
+void MyMoneySchedule::setOccurenceMultiplier(int occmultiplier)
+{
+ m_occurenceMultiplier = occmultiplier < 1 ? 1 : occmultiplier;
+}
+
+void MyMoneySchedule::setType(typeE type)
+{
+ m_type = type;
+}
+
+void MyMoneySchedule::validate(bool id_check) const
+{
+ /* Check the supplied instance is valid...
+ *
+ * To be valid it must not have the id set and have the following fields set:
+ *
+ * m_occurence
+ * m_type
+ * m_startDate
+ * m_paymentType
+ * m_transaction
+ * the transaction must contain at least one split (two is better ;-) )
+ */
+ if (id_check && !m_id.isEmpty())
+ throw new MYMONEYEXCEPTION("ID for schedule not empty when required");
+
+ if(m_occurence == OCCUR_ANY)
+ throw new MYMONEYEXCEPTION("Invalid occurence type for schedule");
+
+ if(m_type == TYPE_ANY)
+ throw new MYMONEYEXCEPTION("Invalid type for schedule");
+
+ if(!nextDueDate().isValid())
+ throw new MYMONEYEXCEPTION("Invalid next due date for schedule");
+
+ if(m_paymentType == STYPE_ANY)
+ throw new MYMONEYEXCEPTION("Invalid payment type for schedule");
+
+ if(m_transaction.splitCount() == 0)
+ throw new MYMONEYEXCEPTION("Scheduled transaction does not contain splits");
+
+ // Check the payment types
+ switch (m_type)
+ {
+ case TYPE_BILL:
+ if (m_paymentType == STYPE_DIRECTDEPOSIT || m_paymentType == STYPE_MANUALDEPOSIT)
+ throw new MYMONEYEXCEPTION("Invalid payment type for bills");
+ break;
+
+ case TYPE_DEPOSIT:
+ if (m_paymentType == STYPE_DIRECTDEBIT || m_paymentType == STYPE_WRITECHEQUE)
+ throw new MYMONEYEXCEPTION("Invalid payment type for deposits");
+ break;
+
+ case TYPE_ANY:
+ throw new MYMONEYEXCEPTION("Invalid type ANY");
+ break;
+
+ case TYPE_TRANSFER:
+// if (m_paymentType == STYPE_DIRECTDEPOSIT || m_paymentType == STYPE_MANUALDEPOSIT)
+// return false;
+ break;
+
+ case TYPE_LOANPAYMENT:
+ break;
+ }
+}
+
+QDate MyMoneySchedule::adjustedNextPayment(const QDate& refDate) const
+{
+ QDate date(nextPayment(refDate));
+ return date.isValid() ? adjustedDate(date, weekendOption()) : date;
+}
+
+QDate MyMoneySchedule::nextPayment(const QDate& refDate) const
+{
+ // if the enddate is valid and it is before the reference date,
+ // then there will be no more payments.
+ if(m_endDate.isValid() && m_endDate < refDate) {
+ return QDate();
+ }
+
+ QDate paymentDate(nextDueDate());
+
+ if(refDate >= paymentDate) {
+ switch (m_occurence)
+ {
+ case OCCUR_ONCE:
+ // if the lastPayment is already set, then there will be no more payments
+ // otherwise, the start date is the payment date
+ if(m_lastPayment.isValid())
+ return QDate();
+ // if the only payment should have been prior to the reference date,
+ // then don't show it
+ if(paymentDate < refDate)
+ return QDate();
+ break;
+
+ case OCCUR_DAILY:
+ paymentDate = refDate.addDays(m_occurenceMultiplier);
+ break;
+
+ case OCCUR_WEEKLY:
+ {
+ int step = 7*m_occurenceMultiplier;
+ do {
+ paymentDate = paymentDate.addDays(step);
+ }
+ while (paymentDate <= refDate);
+ }
+ break;
+
+ case OCCUR_EVERYHALFMONTH:
+ do
+ {
+ paymentDate = addHalfMonths(paymentDate,m_occurenceMultiplier);
+ }
+ while (paymentDate <= refDate);
+ break;
+
+ case OCCUR_MONTHLY:
+ do {
+ paymentDate = paymentDate.addMonths(m_occurenceMultiplier);
+ fixDate(paymentDate);
+ }
+ while (paymentDate <= refDate);
+ break;
+
+ case OCCUR_YEARLY:
+ do {
+ paymentDate = paymentDate.addYears(m_occurenceMultiplier);
+ fixDate(paymentDate);
+ }
+ while (paymentDate <= refDate);
+ break;
+
+ case OCCUR_ANY:
+ default:
+ paymentDate = QDate();
+ break;
+ }
+ }
+ if(paymentDate.isValid()) {
+ if(m_endDate.isValid() && paymentDate > m_endDate)
+ paymentDate = QDate();
+ }
+
+ if (paymentDate.isValid() && m_recordedPayments.contains(paymentDate))
+ paymentDate = nextPayment(paymentDate);
+
+ return paymentDate;
+}
+
+QValueList<QDate> MyMoneySchedule::paymentDates(const QDate& _startDate, const QDate& _endDate) const
+{
+ QDate paymentDate(nextDueDate());
+ QValueList<QDate> theDates;
+
+ QDate endDate(_endDate);
+ if ( willEnd() && m_endDate < endDate )
+ endDate = m_endDate;
+
+ weekendOptionE option(weekendOption());
+ QDate start_date(adjustedDate(startDate(), option));
+ // if the period specified by the parameters and the period
+ // defined for this schedule don't overlap, then the list remains empty
+ if ((willEnd() && m_endDate < _startDate)
+ || start_date > endDate)
+ return theDates;
+
+ QDate date(adjustedDate(paymentDate, option));
+
+ switch (m_occurence)
+ {
+ case OCCUR_ONCE:
+ if (start_date >= _startDate && start_date <= endDate)
+ theDates.append(start_date);
+ break;
+
+ case OCCUR_DAILY:
+ while (date.isValid() && (date <= endDate))
+ {
+ if (date >= _startDate)
+ theDates.append(date);
+ paymentDate = paymentDate.addDays(m_occurenceMultiplier);
+ date = adjustedDate(paymentDate, option);
+ }
+ break;
+
+ case OCCUR_WEEKLY:
+ {
+ int step = 7*m_occurenceMultiplier;
+ while (date.isValid() && (date <= endDate))
+ {
+ if (date >= _startDate)
+ theDates.append(date);
+ paymentDate = paymentDate.addDays(step);
+ date = adjustedDate(paymentDate, option);
+ }
+ }
+ break;
+
+ case OCCUR_EVERYHALFMONTH:
+ while (date.isValid() && (date <= endDate))
+ {
+ if (date >= _startDate)
+ theDates.append(date);
+ paymentDate = addHalfMonths(paymentDate,m_occurenceMultiplier);
+ date = adjustedDate(paymentDate, option);
+ }
+ break;
+
+ case OCCUR_MONTHLY:
+ while (date.isValid() && (date <= endDate))
+ {
+ if (date >= _startDate)
+ theDates.append(date);
+ paymentDate = paymentDate.addMonths(m_occurenceMultiplier);
+ fixDate(paymentDate);
+ date = adjustedDate(paymentDate, option);
+ }
+ break;
+
+ case OCCUR_YEARLY:
+ while (date.isValid() && (date <= endDate))
+ {
+ if (date >= _startDate)
+ theDates.append(date);
+ paymentDate = paymentDate.addYears(m_occurenceMultiplier);
+ fixDate(paymentDate);
+ date = adjustedDate(paymentDate, option);
+ }
+ break;
+
+ case OCCUR_ANY:
+ default:
+ break;
+ }
+
+ return theDates;
+}
+
+bool MyMoneySchedule::operator <(const MyMoneySchedule& right) const
+{
+ return adjustedNextDueDate() < right.adjustedNextDueDate();
+}
+
+bool MyMoneySchedule::operator ==(const MyMoneySchedule& right) const
+{
+ if ( MyMoneyObject::operator==(right) &&
+ m_occurence == right.m_occurence &&
+ m_occurenceMultiplier == right.m_occurenceMultiplier &&
+ m_type == right.m_type &&
+ m_startDate == right.m_startDate &&
+ m_paymentType == right.m_paymentType &&
+ m_fixed == right.m_fixed &&
+ m_transaction == right.m_transaction &&
+ m_endDate == right.m_endDate &&
+ m_autoEnter == right.m_autoEnter &&
+ m_lastPayment == right.m_lastPayment &&
+ ((m_name.length() == 0 && right.m_name.length() == 0) || (m_name == right.m_name)))
+ return true;
+ return false;
+}
+
+int MyMoneySchedule::transactionsRemaining(void) const
+{
+ int counter=0;
+
+ if (m_endDate.isValid())
+ {
+ QValueList<QDate> dates = paymentDates(m_lastPayment, m_endDate);
+ // Dont include the last payment so -1
+ counter = dates.count();
+ }
+ return counter;
+}
+
+MyMoneyAccount MyMoneySchedule::account(int cnt) const
+{
+ QValueList<MyMoneySplit> splits = m_transaction.splits();
+ QValueList<MyMoneySplit>::ConstIterator it;
+ MyMoneyFile* file = MyMoneyFile::instance();
+ MyMoneyAccount acc;
+
+ // search the first asset or liability account
+ for(it = splits.begin(); it != splits.end() && (acc.id().isEmpty() || cnt); ++it) {
+ try {
+ acc = file->account((*it).accountId());
+ if(acc.isAssetLiability())
+ --cnt;
+
+ if(!cnt)
+ return acc;
+ } catch(MyMoneyException *e) {
+ qWarning("Schedule '%s' references unknown account '%s'", id().data(), (*it).accountId().data());
+ delete e;
+ return MyMoneyAccount();
+ }
+ }
+
+ return MyMoneyAccount();
+}
+
+QDate MyMoneySchedule::dateAfter(int transactions) const
+{
+ int counter=1;
+ QDate paymentDate(startDate());
+
+ if (transactions<=0)
+ return paymentDate;
+
+ switch (m_occurence)
+ {
+ case OCCUR_ONCE:
+ break;
+
+ case OCCUR_DAILY:
+ while (counter++ < transactions)
+ paymentDate = paymentDate.addDays(m_occurenceMultiplier);
+ break;
+
+ case OCCUR_WEEKLY:
+ {
+ int step = 7 * m_occurenceMultiplier;
+ while (counter++ < transactions)
+ paymentDate = paymentDate.addDays(step);
+ }
+ break;
+
+ case OCCUR_EVERYHALFMONTH:
+ paymentDate = addHalfMonths(paymentDate,m_occurenceMultiplier*(transactions-1));
+ break;
+
+ case OCCUR_MONTHLY:
+ while (counter++ < transactions)
+ paymentDate = paymentDate.addMonths(m_occurenceMultiplier);
+ break;
+
+ case OCCUR_YEARLY:
+ while (counter++ < transactions)
+ paymentDate = paymentDate.addYears(m_occurenceMultiplier);
+ break;
+
+ case OCCUR_ANY:
+ default:
+ break;
+ }
+
+ return paymentDate;
+}
+
+bool MyMoneySchedule::isOverdue() const
+{
+ if (isFinished())
+ return false;
+
+ if(adjustedNextDueDate() >= QDate::currentDate())
+ return false;
+
+ return true;
+}
+
+bool MyMoneySchedule::isFinished() const
+{
+ if(!m_lastPayment.isValid())
+ return false;
+
+ if (m_endDate.isValid()) {
+ if(m_lastPayment >= m_endDate
+ || !nextDueDate().isValid()
+ || nextDueDate() > m_endDate)
+ return true;
+ }
+
+ // Check to see if its a once off payment
+ if (m_occurence == MyMoneySchedule::OCCUR_ONCE)
+ return true;
+
+ return false;
+}
+
+bool MyMoneySchedule::hasRecordedPayment(const QDate& date) const
+{
+ // m_lastPayment should always be > recordedPayments()
+ if (m_lastPayment.isValid() && m_lastPayment >= date)
+ return true;
+
+ if (m_recordedPayments.contains(date))
+ return true;
+
+ return false;
+}
+
+void MyMoneySchedule::recordPayment(const QDate& date)
+{
+ m_recordedPayments.append(date);
+}
+
+void MyMoneySchedule::setWeekendOption(const weekendOptionE option)
+{
+ // make sure only valid values are used. Invalid defaults to MoveNothing.
+ switch(option) {
+ case MoveFriday:
+ case MoveMonday:
+ m_weekendOption = option;
+ break;
+
+ default:
+ m_weekendOption = MoveNothing;
+ break;
+ }
+}
+
+void MyMoneySchedule::fixDate(QDate& date) const
+{
+ QDate fixDate(m_startDate);
+ if(fixDate.isValid()
+ && date.day() != fixDate.day()
+ && QDate::isValid(date.year(), date.month(), fixDate.day())) {
+ date.setYMD(date.year(), date.month(), fixDate.day());
+ }
+}
+
+void MyMoneySchedule::writeXML(QDomDocument& document, QDomElement& parent) const
+{
+ QDomElement el = document.createElement("SCHEDULED_TX");
+
+ writeBaseXML(document, el);
+
+ el.setAttribute("name", m_name);
+ el.setAttribute("type", m_type);
+ el.setAttribute("occurence", m_occurence);
+ el.setAttribute("occurenceMultiplier", m_occurenceMultiplier);
+ el.setAttribute("paymentType", m_paymentType);
+ el.setAttribute("startDate", dateToString(m_startDate));
+ el.setAttribute("endDate", dateToString(m_endDate));
+ el.setAttribute("fixed", m_fixed);
+ el.setAttribute("autoEnter", m_autoEnter);
+ el.setAttribute("lastPayment", dateToString(m_lastPayment));
+ el.setAttribute("weekendOption", m_weekendOption);
+
+ //store the payment history for this scheduled task.
+ QValueList<QDate> payments = recordedPayments();
+ QValueList<QDate>::ConstIterator it;
+ QDomElement paymentsElement = document.createElement("PAYMENTS");
+ for (it=payments.begin(); it!=payments.end(); ++it) {
+ QDomElement paymentEntry = document.createElement("PAYMENT");
+ paymentEntry.setAttribute("date", dateToString(*it));
+ paymentsElement.appendChild(paymentEntry);
+ }
+ el.appendChild(paymentsElement);
+
+ //store the transaction data for this task.
+ m_transaction.writeXML(document, el);
+
+ parent.appendChild(el);
+}
+
+bool MyMoneySchedule::hasReferenceTo(const QString& id) const
+{
+ return m_transaction.hasReferenceTo(id);
+}
+
+QString MyMoneySchedule::occurenceToString() const
+{
+ return occurenceToString( occurenceMultiplier(), occurencePeriod() );
+}
+
+QString MyMoneySchedule::occurenceToString(occurenceE occurence)
+{
+ QString occurenceString = I18N_NOOP("Any");
+
+ if(occurence == MyMoneySchedule::OCCUR_ONCE)
+ occurenceString = I18N_NOOP("Once");
+ else if(occurence == MyMoneySchedule::OCCUR_DAILY)
+ occurenceString = I18N_NOOP("Daily");
+ else if(occurence == MyMoneySchedule::OCCUR_WEEKLY)
+ occurenceString = I18N_NOOP("Weekly");
+ else if(occurence == MyMoneySchedule::OCCUR_FORTNIGHTLY)
+ occurenceString = I18N_NOOP("Fortnightly");
+ else if(occurence == MyMoneySchedule::OCCUR_EVERYOTHERWEEK)
+ occurenceString = I18N_NOOP("Every other week");
+ else if(occurence == MyMoneySchedule::OCCUR_EVERYHALFMONTH)
+ occurenceString = I18N_NOOP("Every half month");
+ else if(occurence == MyMoneySchedule::OCCUR_EVERYTHREEWEEKS)
+ occurenceString = I18N_NOOP("Every three weeks");
+ else if(occurence == MyMoneySchedule::OCCUR_EVERYFOURWEEKS)
+ occurenceString = I18N_NOOP("Every four weeks");
+ else if(occurence == MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS)
+ occurenceString = I18N_NOOP("Every thirty days");
+ else if(occurence == MyMoneySchedule::OCCUR_MONTHLY)
+ occurenceString = I18N_NOOP("Monthly");
+ else if(occurence == MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS)
+ occurenceString = I18N_NOOP("Every eight weeks");
+ else if(occurence == MyMoneySchedule::OCCUR_EVERYOTHERMONTH)
+ occurenceString = I18N_NOOP("Every two months");
+ else if(occurence == MyMoneySchedule::OCCUR_EVERYTHREEMONTHS)
+ occurenceString = I18N_NOOP("Every three months");
+ else if(occurence == MyMoneySchedule::OCCUR_QUARTERLY)
+ occurenceString = I18N_NOOP("Quarterly");
+ else if(occurence == MyMoneySchedule::OCCUR_EVERYFOURMONTHS)
+ occurenceString = I18N_NOOP("Every four months");
+ else if(occurence == MyMoneySchedule::OCCUR_TWICEYEARLY)
+ occurenceString = I18N_NOOP("Twice yearly");
+ else if(occurence == MyMoneySchedule::OCCUR_YEARLY)
+ occurenceString = I18N_NOOP("Yearly");
+ else if(occurence == MyMoneySchedule::OCCUR_EVERYOTHERYEAR)
+ occurenceString = I18N_NOOP("Every other year");
+ return occurenceString;
+}
+
+QString MyMoneySchedule::occurenceToString(int mult, occurenceE type)
+{
+ QString occurenceString = I18N_NOOP("Any");
+
+ if (type == MyMoneySchedule::OCCUR_ONCE)
+ switch (mult)
+ {
+ case 1: occurenceString = I18N_NOOP("Once"); break;
+ default: occurenceString = I18N_NOOP(static_cast<QString>("%1 times").arg(mult));
+ }
+ else if(type == MyMoneySchedule::OCCUR_DAILY)
+ switch (mult)
+ {
+ case 1: occurenceString = I18N_NOOP("Daily"); break;
+ case 30: occurenceString = I18N_NOOP("Every thirty days"); break;
+ default: occurenceString = I18N_NOOP(static_cast<QString>("Every %1 days").arg(mult));
+ }
+ else if(type == MyMoneySchedule::OCCUR_WEEKLY)
+ switch (mult)
+ {
+ case 1: occurenceString = I18N_NOOP("Weekly"); break;
+ case 2: occurenceString = I18N_NOOP("Every other week"); break;
+ case 3: occurenceString = I18N_NOOP("Every three weeks"); break;
+ case 4: occurenceString = I18N_NOOP("Every four weeks"); break;
+ case 8: occurenceString = I18N_NOOP("Every eight weeks"); break;
+ default: occurenceString = I18N_NOOP(static_cast<QString>("Every %1 weeks").arg(mult));
+ }
+ else if(type == MyMoneySchedule::OCCUR_EVERYHALFMONTH)
+ switch (mult)
+ {
+ case 1: occurenceString = I18N_NOOP("Every half month"); break;
+ default: occurenceString = I18N_NOOP(static_cast<QString>("Every %1 half months").arg(mult));
+ }
+ else if(type == MyMoneySchedule::OCCUR_MONTHLY)
+ switch (mult)
+ {
+ case 1: occurenceString = I18N_NOOP("Monthly"); break;
+ case 2: occurenceString = I18N_NOOP("Every two months"); break;
+ case 3: occurenceString = I18N_NOOP("Every three months"); break;
+ case 4: occurenceString = I18N_NOOP("Every four months"); break;
+ case 6: occurenceString = I18N_NOOP("Twice yearly"); break;
+ default: occurenceString = I18N_NOOP(static_cast<QString>("Every %1 months").arg(mult));
+ }
+ else if(type == MyMoneySchedule::OCCUR_YEARLY)
+ switch (mult)
+ {
+ case 1: occurenceString = I18N_NOOP("Yearly"); break;
+ case 2: occurenceString = I18N_NOOP("Every other year"); break;
+ default: occurenceString = I18N_NOOP(static_cast<QString>("Every %1 years").arg(mult));
+ }
+ return occurenceString;
+}
+
+QString MyMoneySchedule::occurencePeriodToString(MyMoneySchedule::occurenceE type)
+{
+ QString occurenceString = I18N_NOOP("Any");
+
+ if(type == MyMoneySchedule::OCCUR_ONCE)
+ occurenceString = I18N_NOOP("Once");
+ else if(type == MyMoneySchedule::OCCUR_DAILY)
+ occurenceString = I18N_NOOP("Day");
+ else if(type == MyMoneySchedule::OCCUR_WEEKLY)
+ occurenceString = I18N_NOOP("Week");
+ else if(type == MyMoneySchedule::OCCUR_EVERYHALFMONTH)
+ occurenceString = I18N_NOOP("Half-month");
+ else if(type == MyMoneySchedule::OCCUR_MONTHLY)
+ occurenceString = I18N_NOOP("Month");
+ else if(type == MyMoneySchedule::OCCUR_YEARLY)
+ occurenceString = I18N_NOOP("Year");
+ return occurenceString;
+}
+
+QString MyMoneySchedule::scheduleTypeToString(MyMoneySchedule::typeE type)
+{
+ QString text;
+
+ switch (type) {
+ case MyMoneySchedule::TYPE_BILL:
+ text = I18N_NOOP("Bill");
+ break;
+ case MyMoneySchedule::TYPE_DEPOSIT:
+ text = I18N_NOOP("Deposit");
+ break;
+ case MyMoneySchedule::TYPE_TRANSFER:
+ text = I18N_NOOP("Transfer");
+ break;
+ case MyMoneySchedule::TYPE_LOANPAYMENT:
+ text = I18N_NOOP("Loan payment");
+ break;
+ case MyMoneySchedule::TYPE_ANY:
+ default:
+ text = I18N_NOOP("Unknown");
+ }
+ return text;
+}
+
+
+QString MyMoneySchedule::paymentMethodToString(MyMoneySchedule::paymentTypeE paymentType)
+{
+ QString text;
+
+ switch (paymentType) {
+ case MyMoneySchedule::STYPE_DIRECTDEBIT:
+ text = I18N_NOOP("Direct debit");
+ break;
+ case MyMoneySchedule::STYPE_DIRECTDEPOSIT:
+ text = I18N_NOOP("Direct deposit");
+ break;
+ case MyMoneySchedule::STYPE_MANUALDEPOSIT:
+ text = I18N_NOOP("Manual deposit");
+ break;
+ case MyMoneySchedule::STYPE_OTHER:
+ text = I18N_NOOP("Other");
+ break;
+ case MyMoneySchedule::STYPE_WRITECHEQUE:
+ text = I18N_NOOP("Write check");
+ break;
+ case MyMoneySchedule::STYPE_STANDINGORDER:
+ text = I18N_NOOP("Standing order");
+ break;
+ case MyMoneySchedule::STYPE_BANKTRANSFER:
+ text = I18N_NOOP("Bank transfer");
+ break;
+ case MyMoneySchedule::STYPE_ANY:
+ text = I18N_NOOP("Any (Error)");
+ break;
+ }
+ return text;
+}
+
+QString MyMoneySchedule::weekendOptionToString(MyMoneySchedule::weekendOptionE weekendOption)
+{
+ QString text;
+
+ switch (weekendOption) {
+ case MyMoneySchedule::MoveFriday:
+ text = I18N_NOOP("Change the date to the previous Friday");
+ break;
+ case MyMoneySchedule::MoveMonday:
+ text = I18N_NOOP("Change the date to the next Monday");
+ break;
+ case MyMoneySchedule::MoveNothing:
+ text = I18N_NOOP("Do Nothing");
+ break;
+ }
+ return text;
+}
+
+// until we don't have the means to store the value
+// of the variation, we default to 10% in case this
+// scheduled transaction is marked 'not fixed'.
+//
+// ipwizard 2009-04-18
+
+int MyMoneySchedule::variation(void) const
+{
+ int rc = 0;
+ if(!isFixed()) {
+ rc = 10;
+#if 0
+ QString var = value("kmm-variation");
+ if(!var.isEmpty())
+ rc = var.toInt();
+#endif
+ }
+ return rc;
+}
+
+void MyMoneySchedule::setVariation(int var)
+{
+#if 0
+ deletePair("kmm-variation");
+ if(var != 0)
+ setValue("kmm-variation", QString("%1").arg(var));
+#endif
+}
+
+int MyMoneySchedule::eventsPerYear(MyMoneySchedule::occurenceE occurence)
+{
+ int rc = 0;
+
+ switch(occurence) {
+ case MyMoneySchedule::OCCUR_DAILY:
+ rc = 365;
+ break;
+ case MyMoneySchedule::OCCUR_WEEKLY:
+ rc = 52;
+ break;
+ case MyMoneySchedule::OCCUR_FORTNIGHTLY:
+ rc = 26;
+ break;
+ case MyMoneySchedule::OCCUR_EVERYOTHERWEEK:
+ rc = 26;
+ break;
+ case MyMoneySchedule::OCCUR_EVERYHALFMONTH:
+ rc = 24;
+ break;
+ case MyMoneySchedule::OCCUR_EVERYTHREEWEEKS:
+ rc = 17;
+ break;
+ case MyMoneySchedule::OCCUR_EVERYFOURWEEKS:
+ rc = 13;
+ break;
+ case MyMoneySchedule::OCCUR_MONTHLY:
+ case MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS:
+ rc = 12;
+ break;
+ case MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS:
+ rc = 6;
+ break;
+ case MyMoneySchedule::OCCUR_EVERYOTHERMONTH:
+ rc = 6;
+ break;
+ case MyMoneySchedule::OCCUR_EVERYTHREEMONTHS:
+ case MyMoneySchedule::OCCUR_QUARTERLY:
+ rc = 4;
+ break;
+ case MyMoneySchedule::OCCUR_EVERYFOURMONTHS:
+ rc = 3;
+ break;
+ case MyMoneySchedule::OCCUR_TWICEYEARLY:
+ rc = 2;
+ break;
+ case MyMoneySchedule::OCCUR_YEARLY:
+ rc = 1;
+ break;
+ default:
+ qWarning("Occurence not supported by financial calculator");
+ }
+
+ return rc;
+}
+
+int MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::occurenceE occurence)
+{
+ int rc = 0;
+
+ switch(occurence) {
+ case MyMoneySchedule::OCCUR_DAILY:
+ rc = 1;
+ break;
+ case MyMoneySchedule::OCCUR_WEEKLY:
+ rc = 7;
+ break;
+ case MyMoneySchedule::OCCUR_FORTNIGHTLY:
+ rc = 14;
+ break;
+ case MyMoneySchedule::OCCUR_EVERYOTHERWEEK:
+ rc = 14;
+ break;
+ case MyMoneySchedule::OCCUR_EVERYHALFMONTH:
+ rc = 15;
+ break;
+ case MyMoneySchedule::OCCUR_EVERYTHREEWEEKS:
+ rc = 21;
+ break;
+ case MyMoneySchedule::OCCUR_EVERYFOURWEEKS:
+ rc = 28;
+ break;
+ case MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS:
+ rc = 30;
+ break;
+ case MyMoneySchedule::OCCUR_MONTHLY:
+ rc = 30;
+ break;
+ case MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS:
+ rc = 56;
+ break;
+ case MyMoneySchedule::OCCUR_EVERYOTHERMONTH:
+ rc = 60;
+ break;
+ case MyMoneySchedule::OCCUR_EVERYTHREEMONTHS:
+ case MyMoneySchedule::OCCUR_QUARTERLY:
+ rc = 90;
+ break;
+ case MyMoneySchedule::OCCUR_EVERYFOURMONTHS:
+ rc = 120;
+ break;
+ case MyMoneySchedule::OCCUR_TWICEYEARLY:
+ rc = 180;
+ break;
+ case MyMoneySchedule::OCCUR_YEARLY:
+ rc = 360;
+ break;
+ default:
+ qWarning("Occurence not supported by financial calculator");
+ }
+
+ return rc;
+}
+
+QDate MyMoneySchedule::addHalfMonths( QDate date, int mult ) const
+{
+ QDate newdate = date;
+ int d, dm;
+ if ( mult > 0 )
+ {
+ d = newdate.day();
+ if ( d <= 12 )
+ {
+ if ( mult % 2 == 0 )
+ newdate = newdate.addMonths(mult>>1);
+ else
+ newdate = newdate.addMonths(mult>>1).addDays(15);
+ }
+ else
+ for ( int i = 0; i < mult; i++ )
+ {
+ if ( d <= 13 )
+ newdate = newdate.addDays(15);
+ else
+ {
+ dm = newdate.daysInMonth();
+ if ( d == 14 )
+ newdate = newdate.addDays(( dm < 30 ) ? dm - d : 15);
+ else if ( d == 15 )
+ newdate = newdate.addDays(dm - d);
+ else if ( d == dm )
+ newdate = newdate.addDays(15 - d).addMonths(1);
+ else
+ newdate = newdate.addDays(-15).addMonths(1);
+ }
+ d = newdate.day();
+ }
+ }
+ else if ( mult < 0 ) // Go backwards
+ for ( int i = 0; i > mult; i-- )
+ {
+ d = newdate.day();
+ dm = newdate.daysInMonth();
+ if ( d > 15 )
+ {
+ dm = newdate.daysInMonth();
+ newdate = newdate.addDays( (d == dm) ? 15 - dm : -15);
+ }
+ else if ( d <= 13 )
+ newdate = newdate.addMonths(-1).addDays(15);
+ else if ( d == 15 )
+ newdate = newdate.addDays(-15);
+ else // 14
+ {
+ newdate = newdate.addMonths(-1);
+ dm = newdate.daysInMonth();
+ newdate = newdate.addDays(( dm < 30 ) ? dm - d : 15 );
+ }
+ }
+ return newdate;
+}
+
+MyMoneySchedule::occurenceE MyMoneySchedule::stringToOccurence(const QString& text)
+{
+ MyMoneySchedule::occurenceE occurence = MyMoneySchedule::OCCUR_ANY;
+ QString tmp = text.lower();
+
+ if(tmp == i18n("Once").lower())
+ occurence = MyMoneySchedule::OCCUR_ONCE;
+ else if(tmp == i18n("Daily").lower())
+ occurence = MyMoneySchedule::OCCUR_DAILY;
+ else if(tmp == i18n("Weekly").lower())
+ occurence = MyMoneySchedule::OCCUR_WEEKLY;
+ else if(tmp == i18n("Fortnightly").lower())
+ occurence = MyMoneySchedule::OCCUR_FORTNIGHTLY;
+ else if(tmp == i18n("Every other week").lower())
+ occurence = MyMoneySchedule::OCCUR_EVERYOTHERWEEK;
+ else if(tmp == i18n("Every half month").lower())
+ occurence = MyMoneySchedule::OCCUR_EVERYHALFMONTH;
+ else if(tmp == i18n("Every three weeks").lower())
+ occurence = MyMoneySchedule::OCCUR_EVERYTHREEWEEKS;
+ else if(tmp == i18n("Every four weeks").lower())
+ occurence = MyMoneySchedule::OCCUR_EVERYFOURWEEKS;
+ else if(tmp == i18n("Every thirty days").lower())
+ occurence = MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS;
+ else if(tmp == i18n("Monthly").lower())
+ occurence = MyMoneySchedule::OCCUR_MONTHLY;
+ else if(tmp == i18n("Every eight weeks").lower())
+ occurence = MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS;
+ else if(tmp == i18n("Every two months").lower())
+ occurence = MyMoneySchedule::OCCUR_EVERYOTHERMONTH;
+ else if(tmp == i18n("Every three months").lower())
+ occurence = MyMoneySchedule::OCCUR_EVERYTHREEMONTHS;
+ else if(tmp == i18n("Quarterly").lower())
+ occurence = MyMoneySchedule::OCCUR_QUARTERLY;
+ else if(tmp == i18n("Every four months").lower())
+ occurence = MyMoneySchedule::OCCUR_EVERYFOURMONTHS;
+ else if(tmp == i18n("Twice yearly").lower())
+ occurence = MyMoneySchedule::OCCUR_TWICEYEARLY;
+ else if(tmp == i18n("Yearly").lower())
+ occurence = MyMoneySchedule::OCCUR_YEARLY;
+ else if(tmp == i18n("Every other year").lower())
+ occurence = MyMoneySchedule::OCCUR_EVERYOTHERYEAR;
+
+ return occurence;
+}
+
+/**
+ * Helper method to convert simple occurence to compound occurence + multiplier
+ *
+ * @param multiplier Returned by reference. Adjusted multiplier
+ * @param occurence Returned by reference. Occurence type
+ */
+void MyMoneySchedule::simpleToCompoundOccurence(int& multiplier,occurenceE& occurence)
+{
+ occurenceE newOcc = occurence;
+ int newMulti = 1;
+ if (occurence == MyMoneySchedule::OCCUR_ONCE ||
+ occurence == MyMoneySchedule::OCCUR_DAILY ||
+ occurence == MyMoneySchedule::OCCUR_WEEKLY ||
+ occurence == MyMoneySchedule::OCCUR_EVERYHALFMONTH ||
+ occurence == MyMoneySchedule::OCCUR_MONTHLY ||
+ occurence == MyMoneySchedule::OCCUR_YEARLY )
+ { // Already a base occurence and multiplier
+ }
+ else if(occurence == MyMoneySchedule::OCCUR_FORTNIGHTLY ||
+ occurence == MyMoneySchedule::OCCUR_EVERYOTHERWEEK)
+ {
+ newOcc = MyMoneySchedule::OCCUR_WEEKLY;
+ newMulti = 2;
+ }
+ else if(occurence == MyMoneySchedule::OCCUR_EVERYTHREEWEEKS)
+ {
+ newOcc = MyMoneySchedule::OCCUR_WEEKLY;
+ newMulti = 3;
+ }
+ else if(occurence == MyMoneySchedule::OCCUR_EVERYFOURWEEKS)
+ {
+ newOcc = MyMoneySchedule::OCCUR_WEEKLY;
+ newMulti = 4;
+ }
+ else if(occurence == MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS)
+ {
+ newOcc = MyMoneySchedule::OCCUR_DAILY;
+ newMulti = 30;
+ }
+ else if(occurence == MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS)
+ {
+ newOcc = MyMoneySchedule::OCCUR_WEEKLY;
+ newMulti = 8;
+ }
+ else if(occurence == MyMoneySchedule::OCCUR_EVERYOTHERMONTH)
+ {
+ newOcc = MyMoneySchedule::OCCUR_MONTHLY;
+ newMulti = 2;
+ }
+ else if(occurence == MyMoneySchedule::OCCUR_EVERYTHREEMONTHS ||
+ occurence == MyMoneySchedule::OCCUR_QUARTERLY )
+ {
+ newOcc = MyMoneySchedule::OCCUR_MONTHLY;
+ newMulti = 3;
+ }
+ else if(occurence == MyMoneySchedule::OCCUR_EVERYFOURMONTHS)
+ {
+ newOcc = MyMoneySchedule::OCCUR_MONTHLY;
+ newMulti = 4;
+ }
+ else if(occurence == MyMoneySchedule::OCCUR_TWICEYEARLY)
+ {
+ newOcc = MyMoneySchedule::OCCUR_MONTHLY;
+ newMulti = 6;
+ }
+ else if(occurence == MyMoneySchedule::OCCUR_EVERYOTHERYEAR)
+ {
+ newOcc = MyMoneySchedule::OCCUR_YEARLY;
+ newMulti = 2;
+ }
+ else // Unknown
+ {
+ newOcc = MyMoneySchedule::OCCUR_ANY;
+ newMulti = 1;
+ }
+ if (newOcc != occurence)
+ {
+ occurence = newOcc;
+ multiplier = newMulti == 1 ? multiplier : newMulti * multiplier;
+ }
+}
+
+/**
+ * Helper method to convert compound occurence + multiplier to simple occurence
+ *
+ * @param multiplier Returned by reference. Adjusted multiplier
+ * @param occurence Returned by reference. Occurence type
+ */
+void MyMoneySchedule::compoundToSimpleOccurence(int& multiplier,occurenceE& occurence)
+{
+ occurenceE newOcc = occurence;
+ if(occurence == MyMoneySchedule::OCCUR_ONCE)
+ { // Nothing to do
+ }
+ else if(occurence == MyMoneySchedule::OCCUR_DAILY)
+ {
+ switch (multiplier)
+ {
+ case 1: break;
+ case 30: newOcc = MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS; break;
+ }
+ }
+ else if(newOcc == MyMoneySchedule::OCCUR_WEEKLY)
+ {
+ switch (multiplier)
+ {
+ case 1: break;
+ case 2: newOcc = MyMoneySchedule::OCCUR_EVERYOTHERWEEK; break;
+ case 3: newOcc = MyMoneySchedule::OCCUR_EVERYTHREEWEEKS; break;
+ case 4: newOcc = MyMoneySchedule::OCCUR_EVERYFOURWEEKS; break;
+ case 8: newOcc = MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS; break;
+ }
+ }
+ else if(occurence == MyMoneySchedule::OCCUR_MONTHLY)
+ switch (multiplier)
+ {
+ case 1: break;
+ case 2: newOcc = MyMoneySchedule::OCCUR_EVERYOTHERMONTH; break;
+ case 3: newOcc = MyMoneySchedule::OCCUR_EVERYTHREEMONTHS; break;
+ case 4: newOcc = MyMoneySchedule::OCCUR_EVERYFOURMONTHS; break;
+ case 6: newOcc = MyMoneySchedule::OCCUR_TWICEYEARLY; break;
+ }
+ else if(occurence == MyMoneySchedule::OCCUR_EVERYHALFMONTH)
+ switch (multiplier)
+ {
+ case 1: break;
+ }
+ else if(occurence == MyMoneySchedule::OCCUR_YEARLY)
+ {
+ switch (multiplier)
+ {
+ case 1: break;
+ case 2: newOcc = MyMoneySchedule::OCCUR_EVERYOTHERYEAR; break;
+ }
+ }
+ if (occurence != newOcc ) // Changed to derived type
+ {
+ occurence = newOcc;
+ multiplier = 1;
+ }
+}
diff --git a/kmymoney2/mymoney/mymoneyscheduled.h b/kmymoney2/mymoney/mymoneyscheduled.h
new file mode 100644
index 0000000..46303b2
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyscheduled.h
@@ -0,0 +1,695 @@
+/***************************************************************************
+ mymoneyscheduled.h
+ -------------------
+ copyright : (C) 2000-2002 by Michael Edwardes
+ (C) 2007 by Thomas Baumgart
+ email : mte@users.sourceforge.net
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYSCHEDULED_H
+#define MYMONEYSCHEDULED_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstringlist.h>
+#include <qmap.h>
+#include <qdatetime.h>
+
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "mymoneytransaction.h"
+#include "mymoneyaccount.h"
+#include <kmymoney/export.h>
+#include <kmymoney/mymoneyobject.h>
+
+class MyMoneyStorageANON;
+
+/**
+ * @author Michael Edwardes
+ */
+
+/**
+ * This class represents a schedule. (A series of bills, deposits or
+ * transfers).
+ *
+ * @short A class to represent a schedule.
+ * @see MyMoneyScheduled
+ */
+class KMYMONEY_EXPORT MyMoneySchedule : public MyMoneyObject
+{
+ friend class MyMoneyStorageANON;
+public:
+ /**
+ * This enum is used to describe all the possible schedule frequencies.
+ * The special entry, OCCUR_ANY, is used to combine all the other types.
+ */
+ enum occurenceE { OCCUR_ANY=0, OCCUR_ONCE=1, OCCUR_DAILY=2, OCCUR_WEEKLY=4, OCCUR_FORTNIGHTLY=8,
+ OCCUR_EVERYOTHERWEEK=16,
+ OCCUR_EVERYHALFMONTH=18,
+ OCCUR_EVERYTHREEWEEKS=20,
+ OCCUR_EVERYTHIRTYDAYS=30,
+ OCCUR_MONTHLY=32, OCCUR_EVERYFOURWEEKS=64,
+ OCCUR_EVERYEIGHTWEEKS=126,
+ OCCUR_EVERYOTHERMONTH=128, OCCUR_EVERYTHREEMONTHS=256,
+ OCCUR_TWICEYEARLY=1024, OCCUR_EVERYOTHERYEAR=2048, OCCUR_QUARTERLY=4096,
+ OCCUR_EVERYFOURMONTHS=8192, OCCUR_YEARLY=16384
+ };
+
+ /**
+ * This enum is used to describe the schedule type.
+ */
+ enum typeE { TYPE_ANY=0, TYPE_BILL=1, TYPE_DEPOSIT=2, TYPE_TRANSFER=4, TYPE_LOANPAYMENT=5 };
+
+ /**
+ * This enum is used to describe the schedule's payment type.
+ */
+ enum paymentTypeE { STYPE_ANY=0, STYPE_DIRECTDEBIT=1, STYPE_DIRECTDEPOSIT=2,
+ STYPE_MANUALDEPOSIT=4, STYPE_OTHER=8,
+ STYPE_WRITECHEQUE=16,
+ STYPE_STANDINGORDER=32,
+ STYPE_BANKTRANSFER=64 };
+
+ /**
+ * This enum is used by the auto-commit functionality.
+ *
+ * Depending upon the value of m_weekendOption the schedule can
+ * be entered on a different date
+ **/
+ enum weekendOptionE { MoveFriday=0, MoveMonday=1, MoveNothing=2 };
+
+ /**
+ * Standard constructor
+ */
+ MyMoneySchedule();
+
+ /**
+ * Constructor for initialising the object.
+ *
+ * Please note that the optional fields are not set and the transaction
+ * MUST be set before it can be used.
+ *
+ * @a startDate is not used anymore and internally set to QDate()
+ */
+ MyMoneySchedule(const QString& name, typeE type, occurenceE occurence, int occurenceMultiplier,
+ paymentTypeE paymentType, const QDate& startDate, const QDate& endDate, bool fixed, bool autoEnter);
+
+ MyMoneySchedule(const QDomElement& node);
+
+ MyMoneySchedule(const QString& id, const MyMoneySchedule& right);
+
+ /**
+ * Standard destructor
+ */
+ ~MyMoneySchedule() {}
+
+ /**
+ * Simple get method that returns the occurence frequency.
+ *
+ * @return occurenceE The instance frequency.
+ */
+ occurenceE occurence(void) const;
+
+ /**
+ * Simple get method that returns the occurence period
+ * multiplier and occurence
+ *
+ * @return occurenceE The instance period
+ *
+ */
+ occurenceE occurencePeriod(void) const { return m_occurence; }
+
+ /**
+ * Simple get method that returns the occurence period multiplier.
+ *
+ * @return int The frequency multiplier
+ */
+ int occurenceMultiplier(void) const { return m_occurenceMultiplier; }
+
+ /**
+ * Simple get method that returns the schedule type.
+ *
+ * @return typeE The instance type.
+ */
+ typeE type(void) const { return m_type; }
+
+ /**
+ * Simple get method that returns the schedule startDate. If
+ * the schedule has been executed once, the date of the first
+ * execution is returned. Otherwise, the next due date is
+ * returned.
+ *
+ * @return reference to QDate containing the start date.
+ */
+ const QDate& startDate(void) const;
+
+ /**
+ * Simple get method that returns the schedule paymentType.
+ *
+ * @return paymentTypeE The instance paymentType.
+ */
+ paymentTypeE paymentType(void) const { return m_paymentType; }
+
+ /**
+ * Simple get method that returns true if the schedule is fixed.
+ *
+ * @return bool To indicate whether the instance is fixed.
+ */
+ bool isFixed(void) const { return m_fixed; }
+
+ /**
+ * Simple get method that returns true if the schedule will end
+ * at some time.
+ *
+ * @return bool Indicates whether the instance will end.
+ */
+ bool willEnd(void) const { return m_endDate.isValid(); }
+
+ /**
+ * Simple get method that returns the number of transactions remaining.
+ *
+ * @return int The number of transactions remaining for the instance.
+ */
+ int transactionsRemaining(void) const;
+
+ /**
+ * Simple get method that returns the schedule end date.
+ *
+ * @return QDate The end date for the instance.
+ */
+ const QDate& endDate(void) const { return m_endDate; }
+
+ /**
+ * Simple get method that returns true if the transaction should be
+ * automatically entered into the register.
+ *
+ * @return bool Indicates whether the instance will be automatically entered.
+ */
+ bool autoEnter(void) const { return m_autoEnter; }
+
+ /**
+ * Simple get method that returns the transaction data for the schedule.
+ *
+ * @return MyMoneyTransaction The transaction data for the instance.
+ */
+ const MyMoneyTransaction& transaction(void) const { return m_transaction; }
+
+ /**
+ * Simple method that returns the schedules last payment. If the
+ * schedule has never been executed, QDate() will be returned.
+ *
+ * @return QDate The last payment for the schedule.
+ */
+ const QDate& lastPayment(void) const { return m_lastPayment; }
+
+ /**
+ * Simple method that returns the next due date for the schedule.
+ *
+ * @return reference to QDate containing the next due date.
+ *
+ * @note The date returned can represent a value that is past
+ * a possible end of the schedule. Make sure to consider
+ * the return value of isFinished() when using the value returned.
+ */
+ const QDate& nextDueDate(void) const;
+
+ /**
+ * This method adjusts returns the next due date adjusted
+ * according to the rules specified by the schedule's weekend option.
+ *
+ * @return QDate containing the adjusted next due date. If the
+ * schedule is finished (@sa isFinished()) then the method
+ * returns an invalid QDate.
+ *
+ * @sa weekendOption()
+ * @sa adjustedDate()
+ */
+ QDate adjustedNextDueDate(void) const;
+
+ /**
+ * This method adjusts returns the date adjusted according to the
+ * rules specified by the schedule's weekend option.
+ *
+ * @return QDate containing the adjusted date.
+ */
+ QDate adjustedDate(QDate date, weekendOptionE option) const;
+
+ /**
+ * Get the weekendOption that determines how the schedule check code
+ * will enter transactions that occur on a weekend.
+ *
+ * This not used by MyMoneySchedule but by the support code.
+ **/
+ weekendOptionE weekendOption(void) const { return m_weekendOption; }
+
+ /**
+ * Simple method that sets the frequency for the schedule.
+ *
+ * @param occ The new occurence (frequency).
+ * @return none
+ */
+ void setOccurence(occurenceE occ);
+
+ /**
+ * Simple method that sets the schedule period
+ *
+ * @param occ The new occurence period (frequency)
+ * @return none
+ */
+ void setOccurencePeriod(occurenceE occ);
+
+ /**
+ * Simple method that sets the frequency multiplier for the schedule.
+ *
+ * @param occmultiplier The new occurence (frequency) multiplier.
+ * @return none
+ */
+ void setOccurenceMultiplier(int occmultiplier);
+
+ /**
+ * Simple method that sets the type for the schedule.
+ *
+ * @param type The new type.
+ * @return none
+ */
+ void setType(typeE type);
+
+ /**
+ * Simple method that sets the start date for the schedule.
+ *
+ * @param date The new start date.
+ * @return none
+ */
+ void setStartDate(const QDate& date);
+
+ /**
+ * Simple method that sets the payment type for the schedule.
+ *
+ * @param type The new payment type.
+ * @return none
+ */
+ void setPaymentType(paymentTypeE type);
+
+ /**
+ * Simple method to set whether the schedule is fixed or not.
+ *
+ * @param fixed boolean to indicate whether the instance is fixed.
+ * @return none
+ */
+ void setFixed(bool fixed);
+
+ /**
+ * Simple method that sets the transaction for the schedule.
+ * The transaction must have a valid postDate set, otherwise
+ * it will not be accepted.
+ *
+ * @param transaction The new transaction.
+ * @return none
+ */
+ void setTransaction(const MyMoneyTransaction& transaction);
+
+ /**
+ * Simple set method to set the end date for the schedule.
+ *
+ * @param date The new end date.
+ * @return none
+ */
+ void setEndDate(const QDate& date);
+
+ /**
+ * Simple set method to set whether this transaction should be automatically
+ * entered into the journal whenever it is due.
+ *
+ * @param autoenter boolean to indicate whether we need to automatically
+ * enter the transaction.
+ * @return none
+ */
+ void setAutoEnter(bool autoenter);
+
+ /**
+ * Simple set method to set the schedule's next payment date.
+ *
+ * @param date The next payment date.
+ * @return none
+ */
+ void setNextDueDate(const QDate& date);
+
+ /**
+ * Simple set method to set the schedule's last payment. If
+ * this method is called for the first time on the object,
+ * the @a m_startDate member will be set to @a date as well.
+ *
+ * This method should be called whenever a schedule is entered or skipped.
+ *
+ * @param date The last payment date.
+ * @return none
+ */
+ void setLastPayment(const QDate& date);
+
+ /**
+ * Set the weekendOption that determines how the schedule check code
+ * will enter transactions that occur on a weekend. The following values
+ * are valid:
+ *
+ * - MoveNothing: don't modify date
+ * - MoveFriday: modify the date to the previous friday
+ * - MoveMonday: modify the date to the following monday
+ *
+ * If an invalid option is given, the option is set to MoveNothing.
+ *
+ * @param option See list in description
+ * @return none
+ *
+ * @note This not used by MyMoneySchedule but by the support code.
+ **/
+ void setWeekendOption(const weekendOptionE option);
+
+ /**
+ * Validates the schedule instance.
+ *
+ * Makes sure the paymentType matches the type and that the required
+ * fields have been set.
+ *
+ * @param id_check if @p true, the method will check for an empty id.
+ * if @p false, this check is skipped. Default is @p true.
+ *
+ * @return If this method returns, all checks are passed. Otherwise,
+ * it will throw a MyMoneyException object.
+ *
+ * @exception MyMoneyException with detailed error information is thrown
+ * in case of failure of any check.
+ */
+ void validate(bool id_check=true) const;
+
+ /**
+ * Calculates the date of the next payment adjusted according to the
+ * rules specified by the schedule's weekend option.
+ *
+ * @param refDate The reference date from which the next payment
+ * date will be calculated (defaults to current date)
+ *
+ * @return QDate The adjusted date the next payment is due. This date is
+ * always past @a refDate. In case of an error or if there
+ * are no more payments then an empty/invalid QDate() will
+ * be returned.
+ */
+ QDate adjustedNextPayment(const QDate& refDate = QDate::currentDate()) const;
+
+ /**
+ * Calculates the date of the next payment.
+ *
+ * @param refDate The reference date from which the next payment
+ * date will be calculated (defaults to current date)
+ *
+ * @return QDate The date the next payment is due. This date is
+ * always past @a refDate. In case of an error or
+ * if there is no more payments then an empty/invalid QDate()
+ * will be returned.
+ */
+ QDate nextPayment(const QDate& refDate = QDate::currentDate()) const;
+
+ /**
+ * Calculates the dates of the payment over a certain period of time.
+ *
+ * An empty list is returned for no payments or error.
+ *
+ * @param startDate The start date for the range calculations
+ * @param endDate The end date for the range calculations.
+ * @return QValueList<QDate> The dates on which the payments are due.
+ */
+ QValueList<QDate> paymentDates(const QDate& startDate, const QDate& endDate) const;
+
+ /**
+ * Returns the instances name
+ *
+ * @return The name
+ */
+ const QString& name(void) const { return m_name; }
+
+ /**
+ * Changes the instance name
+ *
+ * @param nm The new name
+ * @return none
+ */
+ void setName(const QString& nm);
+
+ bool operator ==(const MyMoneySchedule& right) const;
+ bool operator !=(const MyMoneySchedule& right) const { return ! operator==(right); }
+
+ bool operator <(const MyMoneySchedule& right) const;
+
+ MyMoneyAccount account(int cnt = 1) const;
+ MyMoneyAccount transferAccount(void) const { return account(2); };
+ QDate dateAfter(int transactions) const;
+
+ bool isOverdue() const;
+ bool isFinished() const;
+ bool hasRecordedPayment(const QDate&) const;
+ void recordPayment(const QDate&);
+ QValueList<QDate> recordedPayments(void) const { return m_recordedPayments; }
+
+ void writeXML(QDomDocument& document, QDomElement& parent) const;
+
+ /**
+ * This method checks if a reference to the given object exists. It returns,
+ * a @p true if the object is referencing the one requested by the
+ * parameter @p id. If it does not, this method returns @p false.
+ *
+ * @param id id of the object to be checked for references
+ * @retval true This object references object with id @p id.
+ * @retval false This object does not reference the object with id @p id.
+ */
+ virtual bool hasReferenceTo(const QString& id) const;
+
+ /**
+ * Returns the human-readable format of Schedule's occurence
+ *
+ * @return QString representing the human readable format
+ */
+ QString occurenceToString() const;
+
+ /**
+ * This method is used to convert the occurence type from it's
+ * internal representation into a human readable format.
+ *
+ * @param type numerical representation of the MyMoneySchedule
+ * occurence type
+ *
+ * @return QString representing the human readable format
+ */
+ static QString occurenceToString(occurenceE type);
+
+ /**
+ * This method is used to convert a multiplier and base occurence type
+ * from it's internal representation into a human readable format.
+ * When multiplier * occurence is equivalent to a simple occurence
+ * the method returns the same as occurenceToString of the simple occurence
+ *
+ * @param mult occurence multiplier
+ * @param type occurence period
+ *
+ * @return QString representing the human readable format
+ */
+ static QString occurenceToString(int mult, occurenceE type);
+
+ /**
+ * This method is used to convert an occurence period from
+ * it's internal representation into a human-readable format.
+ *
+ * @param type numerical representation of the MyMoneySchedule
+ * occurence type
+ *
+ * @return QString representing the human readable format
+ */
+ static QString occurencePeriodToString(occurenceE type);
+
+ /**
+ * This method is used to convert the payment type from it's
+ * internal representation into a human readable format.
+ *
+ * @param paymentType numerical representation of the MyMoneySchedule
+ * payment type
+ *
+ * @return QString representing the human readable format
+ */
+ static QString paymentMethodToString(MyMoneySchedule::paymentTypeE paymentType);
+
+ /**
+ * This method is used to convert the schedule weekend option from it's
+ * internal representation into a human readable format.
+ *
+ * @param weekendOption numerical representation of the MyMoneySchedule
+ * weekend option
+ *
+ * @return QString representing the human readable format
+ */
+ static QString weekendOptionToString(MyMoneySchedule::weekendOptionE weekendOption);
+
+ /**
+ * This method is used to convert the schedule type from it's
+ * internal representation into a human readable format.
+ *
+ * @param type numerical representation of the MyMoneySchedule
+ * schedule type
+ *
+ * @return QString representing the human readable format
+ */
+ static QString scheduleTypeToString(MyMoneySchedule::typeE type);
+
+ int variation(void) const;
+ void setVariation(int var);
+
+ /**
+ *
+ * Convert an occurence to the maximum number of events possible during a single
+ * calendar year.
+ * A fortnight is treated as 15 days.
+ *
+ * @param occurence The occurence
+ *
+ * @return int Number of days between events
+ */
+ static int eventsPerYear(MyMoneySchedule::occurenceE occurence);
+
+ /**
+ *
+ * Convert an occurence to the number of days between events
+ * Treats a month as 30 days.
+ * Treats a fortnight as 15 days.
+ *
+ * @param occurence The occurence
+ *
+ * @return int Number of days between events
+ */
+ static int daysBetweenEvents(MyMoneySchedule::occurenceE occurence);
+
+ /**
+ * Helper method to convert simple occurence to compound occurence + multiplier
+ *
+ * @param multiplier Returned by reference. Adjusted multiplier
+ * @param occurence Returned by reference. Occurence type
+ */
+ static void simpleToCompoundOccurence(int& multiplier,occurenceE& occurence);
+
+ /**
+ * Helper method to convert compound occurence + multiplier to simple occurence
+ *
+ * @param multiplier Returned by reference. Adjusted multiplier
+ * @param occurence Returned by reference. Occurence type
+ */
+ static void compoundToSimpleOccurence(int& multiplier,occurenceE& occurence);
+
+ /**
+ * This method is used to convert the occurence type from the
+ * human readable form into it's internal representation.
+ *
+ * @param text reference to QString representing the human readable format
+ * @return numerical representation of the occurence
+ */
+ static MyMoneySchedule::occurenceE stringToOccurence(const QString& text);
+
+private:
+ /**
+ * This method forces the day of the passed @p date to
+ * be the day of the start date of this schedule kept
+ * in m_startDate. It is internally used when calculating
+ * the payment dates over several periods.
+ *
+ * @param date reference to QDate object to be checked and adjusted
+ */
+ void fixDate(QDate& date) const;
+
+ /**
+ * Simple method that sets the transaction for the schedule.
+ * The transaction must have a valid postDate set, otherwise
+ * it will not be accepted. This test is bypassed, if @a noDateCheck
+ * is set to true
+ *
+ * @param transaction The new transaction.
+ * @param noDateCheck if @a true, the date check is bypassed
+ * @return none
+ */
+ void setTransaction(const MyMoneyTransaction& transaction, bool noDateCheck);
+
+ /**
+ * This method adds a number of Half Months to the given Date.
+ * This is used for OCCUR_EVERYHALFMONTH occurences.
+ * The addition uses the following rules to add a half month:
+ * Day 1-13: add 15 days
+ * Day 14: add 15 days (except February: the last day of the month)
+ * Day 15: last day of the month
+ * Day 16-29 (not last day in February): subtract 15 days and add 1 month
+ * 30 and last day: 15th of next month
+ *
+ * This calculation pairs days 1 to 12 with 16 to 27.
+ * Day 15 is paired with the last day of every month.
+ * Repeated addition has issues in the following cases:
+ * - Days 13 to 14 are paired with 28 to 29 until addition hits the last day of February
+ * after which the (15,last) pair will be used.
+ * - Addition from Day 30 leads immediately to the (15th,last) day pair.
+ *
+ * @param date The date
+ * @param mult The number of half months to add. Default is 1.
+ *
+ * @return QDate date with mult half months added
+ */
+ QDate addHalfMonths( QDate date, int mult = 1 ) const;
+
+private:
+ /// Its occurence
+ occurenceE m_occurence;
+
+ /// Its occurence multiplier
+ int m_occurenceMultiplier;
+
+ /// Its type
+ typeE m_type;
+
+ /// The date the schedule commences
+ QDate m_startDate;
+
+ /// The payment type
+ paymentTypeE m_paymentType;
+
+ /// Can the amount vary
+ bool m_fixed;
+
+ /// The, possibly estimated, amount plus all other relevant details
+ MyMoneyTransaction m_transaction;
+
+ /// The last transaction date if the schedule does end at a fixed date
+ QDate m_endDate;
+
+ /// Enter the transaction into the register automatically
+ bool m_autoEnter;
+
+ /// Internal date used for calculations
+ QDate m_lastPayment;
+
+ /// The name
+ QString m_name;
+
+ /// The recorded payments
+ QValueList<QDate> m_recordedPayments;
+
+ /// The weekend option
+ weekendOptionE m_weekendOption;
+};
+#endif
diff --git a/kmymoney2/mymoney/mymoneyscheduletest.cpp b/kmymoney2/mymoney/mymoneyscheduletest.cpp
new file mode 100644
index 0000000..0a4a380
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyscheduletest.cpp
@@ -0,0 +1,1909 @@
+/***************************************************************************
+ mymoneyscheduletest.cpp
+ -------------------
+ copyright : (C) 2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 internationalization
+#include <klocale.h>
+
+#include "mymoneyscheduletest.h"
+
+#include "mymoneysplit.h"
+#include "mymoneymoney.h"
+
+#include <iostream>
+
+MyMoneyScheduleTest::MyMoneyScheduleTest()
+{
+}
+
+
+void MyMoneyScheduleTest::setUp () {
+}
+
+void MyMoneyScheduleTest::tearDown () {
+}
+
+void MyMoneyScheduleTest::testEmptyConstructor() {
+ MyMoneySchedule s;
+
+ CPPUNIT_ASSERT(s.id().isEmpty());
+ CPPUNIT_ASSERT(s.m_occurence == MyMoneySchedule::OCCUR_ANY);
+ CPPUNIT_ASSERT(s.m_type == MyMoneySchedule::TYPE_ANY);
+ CPPUNIT_ASSERT(s.m_paymentType == MyMoneySchedule::STYPE_ANY);
+ CPPUNIT_ASSERT(s.m_fixed == false);
+ CPPUNIT_ASSERT(!s.m_startDate.isValid());
+ CPPUNIT_ASSERT(!s.m_endDate.isValid());
+ CPPUNIT_ASSERT(!s.m_lastPayment.isValid());
+ CPPUNIT_ASSERT(s.m_autoEnter == false);
+ CPPUNIT_ASSERT(s.m_name.isEmpty());
+ CPPUNIT_ASSERT(s.willEnd() == false);
+}
+
+void MyMoneyScheduleTest::testConstructor() {
+ MyMoneySchedule s( "A Name",
+ MyMoneySchedule::TYPE_BILL,
+ MyMoneySchedule::OCCUR_WEEKLY, 1,
+ MyMoneySchedule::STYPE_DIRECTDEBIT,
+ QDate::currentDate(),
+ QDate(),
+ true,
+ true);
+
+ CPPUNIT_ASSERT(s.type() == MyMoneySchedule::TYPE_BILL);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_WEEKLY);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 1 );
+ CPPUNIT_ASSERT(s.paymentType() == MyMoneySchedule::STYPE_DIRECTDEBIT);
+ CPPUNIT_ASSERT(s.startDate() == QDate());
+ CPPUNIT_ASSERT(s.willEnd() == false);
+ CPPUNIT_ASSERT(s.isFixed() == true);
+ CPPUNIT_ASSERT(s.autoEnter() == true);
+ CPPUNIT_ASSERT(s.name() == "A Name");
+ CPPUNIT_ASSERT(!s.m_endDate.isValid());
+ CPPUNIT_ASSERT(!s.m_lastPayment.isValid());
+}
+
+void MyMoneyScheduleTest::testSetFunctions() {
+ MyMoneySchedule s;
+
+ s.setId("SCHED001");
+ CPPUNIT_ASSERT(s.id() == "SCHED001");
+
+ s.setType(MyMoneySchedule::TYPE_BILL);
+ CPPUNIT_ASSERT(s.type() == MyMoneySchedule::TYPE_BILL);
+
+ s.setEndDate(QDate::currentDate());
+ CPPUNIT_ASSERT(s.endDate() == QDate::currentDate());
+ CPPUNIT_ASSERT(s.willEnd() == true);
+}
+
+void MyMoneyScheduleTest::testCopyConstructor() {
+ MyMoneySchedule s;
+
+ s.setId("SCHED001");
+ s.setType(MyMoneySchedule::TYPE_BILL);
+
+ MyMoneySchedule s2(s);
+
+ CPPUNIT_ASSERT(s.id() == s2.id());
+ CPPUNIT_ASSERT(s.type() == s2.type());
+}
+
+void MyMoneyScheduleTest::testAssignmentConstructor() {
+ MyMoneySchedule s;
+
+ s.setId("SCHED001");
+ s.setType(MyMoneySchedule::TYPE_BILL);
+
+ MyMoneySchedule s2 = s;
+
+ CPPUNIT_ASSERT(s.id() == s2.id());
+ CPPUNIT_ASSERT(s.type() == s2.type());
+}
+
+void MyMoneyScheduleTest::testSingleton() {
+/*
+ MyMoneyScheduled *m = MyMoneyScheduled::instance();
+ CPPUNIT_ASSERT(m!=NULL);
+
+ CPPUNIT_ASSERT(m->m_instance != NULL);
+ CPPUNIT_ASSERT(m->m_nextId == 1);
+*/
+}
+
+void MyMoneyScheduleTest::testAddSchedule()
+{
+/*
+ MyMoneyScheduled *m = MyMoneyScheduled::instance();
+ CPPUNIT_ASSERT(m!=NULL);
+
+ try {
+
+ MyMoneySplit sp1;
+ sp1.setShares(MyMoneyMoney(1));
+ sp1.setValue(MyMoneyMoney(1));
+ sp1.setAccountId("MTE1");
+ sp1.setMemo("MTE1");
+ sp1.setPayeeId("MTE1");
+
+ MyMoneySplit sp2;
+ sp2.setShares(MyMoneyMoney(1));
+ sp2.setValue(MyMoneyMoney(1));
+ sp2.setAccountId("MTE2");
+ sp2.setMemo("MTE2");
+ sp2.setPayeeId("MTE2");
+
+ MyMoneyTransaction t;
+ t.addSplit(sp1);
+ t.addSplit(sp2);
+
+ MyMoneySchedule s1( "s1",
+ MyMoneySchedule::TYPE_BILL,
+ MyMoneySchedule::OCCUR_WEEKLY, 1,
+ MyMoneySchedule::STYPE_DIRECTDEBIT,
+ QDate(2001, 1, 1),
+ false,
+ true,
+ true);
+ s1.setTransaction(t);
+ MyMoneySchedule s2( "s2",
+ MyMoneySchedule::TYPE_DEPOSIT,
+ MyMoneySchedule::OCCUR_MONTHLY, 1,
+ MyMoneySchedule::STYPE_MANUALDEPOSIT,
+ QDate(2001, 2, 1),
+ false,
+ true,
+ true);
+ s2.setTransaction(t);
+ MyMoneySchedule s3( "s3",
+ MyMoneySchedule::TYPE_TRANSFER,
+ MyMoneySchedule::OCCUR_YEARLY, 1,
+ MyMoneySchedule::STYPE_WRITECHEQUE,
+ QDate(2001, 3, 1),
+ false,
+ true,
+ true);
+ s3.setTransaction(t);
+
+
+ m->addSchedule("A000001", s1);
+ m->addSchedule("A000001", s2);
+ m->addSchedule("A000001", s3);
+ } catch(MyMoneyException *e) {
+ char buf[256];
+ sprintf(buf, "Unexpected exception: %s", e->what().latin1());
+ CPPUNIT_FAIL(buf);
+ delete e;
+ }
+
+ CPPUNIT_ASSERT(m->m_nextId == 4);
+ CPPUNIT_ASSERT(m->m_accountsScheduled["A000001"].size() == 3);
+*/
+}
+
+void MyMoneyScheduleTest::testAnyScheduled()
+{
+/*
+ MyMoneyScheduled *m = MyMoneyScheduled::instance();
+ CPPUNIT_ASSERT(m!=NULL);
+
+ // Successes
+ CPPUNIT_ASSERT(m->anyScheduled("A000001"));
+ CPPUNIT_ASSERT(m->anyScheduled("A000001", MyMoneySchedule::TYPE_BILL));
+ CPPUNIT_ASSERT(m->anyScheduled("A000001", MyMoneySchedule::TYPE_DEPOSIT));
+ CPPUNIT_ASSERT(m->anyScheduled("A000001", MyMoneySchedule::TYPE_TRANSFER));
+ CPPUNIT_ASSERT(m->anyScheduled("A000001", MyMoneySchedule::TYPE_ANY,
+ MyMoneySchedule::OCCUR_MONTHLY));
+ CPPUNIT_ASSERT(m->anyScheduled("A000001", MyMoneySchedule::TYPE_ANY,
+ MyMoneySchedule::OCCUR_WEEKLY));
+ CPPUNIT_ASSERT(m->anyScheduled("A000001", MyMoneySchedule::TYPE_ANY,
+ MyMoneySchedule::OCCUR_YEARLY));
+ CPPUNIT_ASSERT(m->anyScheduled("A000001", MyMoneySchedule::TYPE_ANY,
+ MyMoneySchedule::OCCUR_ANY,
+ MyMoneySchedule::STYPE_DIRECTDEBIT));
+ CPPUNIT_ASSERT(m->anyScheduled("A000001", MyMoneySchedule::TYPE_ANY,
+ MyMoneySchedule::OCCUR_ANY,
+ MyMoneySchedule::STYPE_MANUALDEPOSIT));
+ CPPUNIT_ASSERT(m->anyScheduled("A000001", MyMoneySchedule::TYPE_ANY,
+ MyMoneySchedule::OCCUR_ANY,
+ MyMoneySchedule::STYPE_WRITECHEQUE));
+
+ // Failures
+ CPPUNIT_ASSERT(m->anyScheduled("A000001", MyMoneySchedule::TYPE_BILL,
+ MyMoneySchedule::OCCUR_MONTHLY) == false);
+*/
+}
+
+void MyMoneyScheduleTest::testOverdue()
+{
+ MyMoneySchedule sch_overdue;
+ MyMoneySchedule sch_intime;
+
+ // the following checks only work correctly, if currentDate() is
+ // between the 1st and 27th. If it is between 28th and 31st
+ // we don't perform them. Note: this should be fixed.
+ if(QDate::currentDate().day() > 27 || QDate::currentDate().day() == 1) {
+ std::cout << std::endl << "testOverdue() skipped because current day is between 28th and 2nd" << std::endl;
+ return;
+ }
+
+ QDate startDate = QDate::currentDate().addDays(-1).addMonths(-23);
+ QDate lastPaymentDate = QDate::currentDate().addDays(-1).addMonths(-1);
+
+ QString ref = QString(
+ "<!DOCTYPE TEST>\n"
+ "<SCHEDULE-CONTAINER>\n"
+ " <SCHEDULED_TX startDate=\"%1\" autoEnter=\"0\" weekendOption=\"2\" lastPayment=\"%2\" paymentType=\"8\" endDate=\"\" type=\"5\" id=\"SCH0002\" name=\"A Name\" fixed=\"0\" occurenceMultiplier=\"1\" occurence=\"32\" >\n"
+ " <PAYMENTS>\n"
+ " <PAYMENT date=\"%3\" />\n"
+ " </PAYMENTS>\n"
+ " <TRANSACTION postdate=\"\" memo=\"Wohnung:Miete\" id=\"\" commodity=\"EUR\" entrydate=\"\" >\n"
+ " <SPLITS>\n"
+ " <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"96379/100\" action=\"\" number=\"\" reconcileflag=\"2\" memo=\"\" value=\"96379/100\" account=\"A000076\" />\n"
+ " <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"-96379/100\" action=\"\" number=\"\" reconcileflag=\"1\" memo=\"\" value=\"-96379/100\" account=\"A000276\" />\n"
+ " </SPLITS>\n"
+ " <KEYVALUEPAIRS>\n"
+ " <PAIR key=\"key\" value=\"value\" />\n"
+ " </KEYVALUEPAIRS>\n"
+ " </TRANSACTION>\n"
+ " </SCHEDULED_TX>\n"
+ "</SCHEDULE-CONTAINER>\n");
+ QString ref_overdue = ref.arg(startDate.toString(Qt::ISODate))
+ .arg(lastPaymentDate.toString(Qt::ISODate))
+ .arg(lastPaymentDate.toString(Qt::ISODate));
+
+ QString ref_intime = ref.arg(startDate.addDays(1).toString(Qt::ISODate))
+ .arg(lastPaymentDate.addDays(1).toString(Qt::ISODate))
+ .arg(lastPaymentDate.addDays(1).toString(Qt::ISODate));
+
+ QDomDocument doc;
+ QDomElement node;
+
+ // std::cout << ref_intime << std::endl;
+ try {
+ doc.setContent(ref_overdue);
+ node = doc.documentElement().firstChild().toElement();
+ sch_overdue = MyMoneySchedule(node);
+ doc.setContent(ref_intime);
+ node = doc.documentElement().firstChild().toElement();
+ sch_intime = MyMoneySchedule(node);
+
+ CPPUNIT_ASSERT(sch_overdue.isOverdue() == true);
+ CPPUNIT_ASSERT(sch_intime.isOverdue() == false);
+
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+/*
+ MyMoneyScheduled *m = MyMoneyScheduled::instance();
+ CPPUNIT_ASSERT(m!=NULL);
+
+ try
+ {
+ CPPUNIT_ASSERT(m->anyOverdue("A000001"));
+ CPPUNIT_ASSERT(m->anyOverdue("A000001", MyMoneySchedule::TYPE_BILL));
+ CPPUNIT_ASSERT(m->anyOverdue("A000001", MyMoneySchedule::TYPE_TRANSFER));
+ CPPUNIT_ASSERT(m->anyOverdue("A000001", MyMoneySchedule::TYPE_DEPOSIT));
+ } catch(MyMoneyException *e) {
+ char buf[256];
+ sprintf(buf, "Unexpected exception: %s", e->what().latin1());
+ CPPUNIT_FAIL(buf);
+ delete e;
+ }
+*/
+}
+
+void MyMoneyScheduleTest::testGetSchedule()
+{
+/*
+ MyMoneyScheduled *m = MyMoneyScheduled::instance();
+ CPPUNIT_ASSERT(m!=NULL);
+
+ try
+ {
+ MyMoneySchedule s = m->getSchedule("A000001", "SCHED00002");
+
+ CPPUNIT_ASSERT(s.type() == MyMoneySchedule::TYPE_DEPOSIT);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_MONTHLY);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 1);
+ CPPUNIT_ASSERT(s.paymentType() == MyMoneySchedule::STYPE_MANUALDEPOSIT);
+ CPPUNIT_ASSERT(s.startDate() == QDate(2001, 2, 1));
+ CPPUNIT_ASSERT(s.willEnd() == false);
+ CPPUNIT_ASSERT(s.isFixed() == true);
+ CPPUNIT_ASSERT(s.autoEnter() == true);
+
+ MyMoneyTransaction t = s.transaction();
+ CPPUNIT_ASSERT(t.splitCount() == 2);
+
+ s = m->getSchedule("A000001", "SCHED00005");
+
+ CPPUNIT_FAIL("Exception expected while getting schedule SCHED00005");
+ } catch (MyMoneyException *e)
+ {
+ delete e;
+ }
+*/
+}
+
+void MyMoneyScheduleTest::testGetScheduled()
+{
+/*
+ MyMoneyScheduled *m = MyMoneyScheduled::instance();
+ CPPUNIT_ASSERT(m!=NULL);
+
+ try
+ {
+ QValueList<QString> testList;
+
+ testList = m->getScheduled("A000001");
+ CPPUNIT_ASSERT(testList.size() == 3);
+ CPPUNIT_ASSERT(testList[0] == "SCHED00001");
+ CPPUNIT_ASSERT(testList[1] == "SCHED00002");
+ CPPUNIT_ASSERT(testList[2] == "SCHED00003");
+
+ testList = m->getScheduled("A000001", MyMoneySchedule::TYPE_DEPOSIT);
+ CPPUNIT_ASSERT(testList.size() == 1);
+ CPPUNIT_ASSERT(testList[0] == "SCHED00002");
+
+ testList = m->getScheduled("A000001", MyMoneySchedule::TYPE_BILL);
+ CPPUNIT_ASSERT(testList.size() == 1);
+ CPPUNIT_ASSERT(testList[0] == "SCHED00001");
+
+ testList = m->getScheduled("A000001", MyMoneySchedule::TYPE_TRANSFER);
+ CPPUNIT_ASSERT(testList.size() == 1);
+ CPPUNIT_ASSERT(testList[0] == "SCHED00003");
+
+ testList = m->getScheduled("A000001", MyMoneySchedule::TYPE_DEPOSIT,
+ MyMoneySchedule::STYPE_MANUALDEPOSIT,
+ MyMoneySchedule::OCCUR_MONTHLY);
+ CPPUNIT_ASSERT(testList.size() == 1);
+ CPPUNIT_ASSERT(testList[0] == "SCHED00002");
+
+ testList = m->getScheduled("A000001", QDate(2001, 1, 1), QDate(2001, 2, 1));
+ CPPUNIT_ASSERT(testList.size() == 2);
+ CPPUNIT_ASSERT(testList[0] == "SCHED00001");
+ CPPUNIT_ASSERT(testList[1] == "SCHED00002");
+
+ } catch(MyMoneyException *e) {
+ char buf[256];
+ sprintf(buf, "Unexpected exception: %s", e->what().latin1());
+ CPPUNIT_FAIL(buf);
+ delete e;
+ }
+*/
+}
+
+void MyMoneyScheduleTest::testGetOverdue()
+{
+/*
+ MyMoneyScheduled *m = MyMoneyScheduled::instance();
+ CPPUNIT_ASSERT(m!=NULL);
+
+ try
+ {
+ QValueList<QString> testList;
+
+ testList = m->getOverdue("A000001");
+ CPPUNIT_ASSERT(testList.size() == 3);
+ CPPUNIT_ASSERT(testList[0] == "SCHED00001");
+ CPPUNIT_ASSERT(testList[1] == "SCHED00002");
+ CPPUNIT_ASSERT(testList[2] == "SCHED00003");
+
+ testList = m->getOverdue("A000001", MyMoneySchedule::TYPE_DEPOSIT);
+ CPPUNIT_ASSERT(testList.size() == 1);
+ CPPUNIT_ASSERT(testList[0] == "SCHED00002");
+
+ testList = m->getOverdue("A000001", MyMoneySchedule::TYPE_BILL);
+ CPPUNIT_ASSERT(testList.size() == 1);
+ CPPUNIT_ASSERT(testList[0] == "SCHED00001");
+
+ testList = m->getOverdue("A000001", MyMoneySchedule::TYPE_TRANSFER);
+ CPPUNIT_ASSERT(testList.size() == 1);
+ CPPUNIT_ASSERT(testList[0] == "SCHED00003");
+
+ testList = m->getOverdue("A000001", MyMoneySchedule::TYPE_DEPOSIT,
+ MyMoneySchedule::STYPE_MANUALDEPOSIT,
+ MyMoneySchedule::OCCUR_MONTHLY);
+ CPPUNIT_ASSERT(testList.size() == 1);
+ CPPUNIT_ASSERT(testList[0] == "SCHED00002");
+ } catch(MyMoneyException *e) {
+ char buf[256];
+ sprintf(buf, "Unexpected exception: %s", e->what().latin1());
+ CPPUNIT_FAIL(buf);
+ delete e;
+ }
+*/
+}
+
+void MyMoneyScheduleTest::testNextPayment()
+/*
+ * Test for a schedule where a payment hasn't yet been made.
+ * First payment is in the future.
+*/
+{
+ MyMoneySchedule sch;
+ QString future_sched = QString(
+ "<!DOCTYPE TEST>\n"
+ "<SCHEDULE-CONTAINER>\n"
+ "<SCHEDULED_TX startDate=\"2007-02-17\" autoEnter=\"1\" weekendOption=\"2\" lastPayment=\"\" paymentType=\"1\" endDate=\"\" type=\"1\" id=\"SCH000058\" name=\"Car Tax\" fixed=\"1\" occurenceMultiplier=\"1\" occurence=\"16384\" >\n"
+ " <PAYMENTS/>\n"
+ " <TRANSACTION postdate=\"\" memo=\"\" id=\"\" commodity=\"GBP\" entrydate=\"\" >\n"
+ " <SPLITS>\n"
+ " <SPLIT payee=\"P000044\" reconciledate=\"\" shares=\"-15000/100\" action=\"Withdrawal\" bankid=\"\" number=\"\" reconcileflag=\"0\" memo=\"\" value=\"-15000/100\" account=\"A000155\" />\n"
+ " <SPLIT payee=\"\" reconciledate=\"\" shares=\"15000/100\" action=\"Withdrawal\" bankid=\"\" number=\"\" reconcileflag=\"0\" memo=\"\" value=\"15000/100\" account=\"A000182\" />\n"
+ " </SPLITS>\n"
+ " <KEYVALUEPAIRS/>\n"
+ " </TRANSACTION>\n"
+ "</SCHEDULED_TX>\n"
+ "</SCHEDULE-CONTAINER>\n"
+ );
+
+ QDomDocument doc;
+ QDomElement node;
+ doc.setContent(future_sched);
+ node = doc.documentElement().firstChild().toElement();
+
+ try {
+ sch = MyMoneySchedule(node);
+ CPPUNIT_ASSERT(sch.nextPayment(QDate(2007,2,14)) == QDate(2007,2,17));
+ CPPUNIT_ASSERT(sch.nextPayment(QDate(2007,2,17)) == QDate(2008,2,17));
+ CPPUNIT_ASSERT(sch.nextPayment(QDate(2007,2,18)) == QDate(2008,2,17));
+
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+/*
+ MyMoneyScheduled *m = MyMoneyScheduled::instance();
+ CPPUNIT_ASSERT(m!=NULL);
+
+ try
+ {
+ MyMoneySchedule s1 = m->getSchedule("A000001", "SCHED00001");
+ MyMoneySchedule s2 = m->getSchedule("A000001", "SCHED00002");
+ MyMoneySchedule s3 = m->getSchedule("A000001", "SCHED00003");
+
+ QDate nextPayment1 = s1.nextPayment();
+ QDate nextPayment2 = s2.nextPayment();
+ QDate nextPayment3 = s3.nextPayment();
+
+ CPPUNIT_ASSERT(nextPayment1.year() != 1900);
+ CPPUNIT_ASSERT(nextPayment2.year() != 1900);
+ CPPUNIT_ASSERT(nextPayment3.year() != 1900);
+ } catch (MyMoneyException *e)
+ {
+ CPPUNIT_FAIL("Unexpected exception");
+ delete e;
+ }
+*/
+}
+
+void MyMoneyScheduleTest::testAddHalfMonths()
+{
+ // addHalfMonths is private
+ // Test a Schedule with occurence OCCUR_EVERYHALFMONTH using nextPayment
+ MyMoneySchedule s;
+ s.setStartDate(QDate(2007, 1, 1));
+ s.setOccurence(MyMoneySchedule::OCCUR_EVERYHALFMONTH);
+ s.setNextDueDate(s.startDate());
+ s.setLastPayment(s.startDate());
+
+ QString format("yyyy-MM-dd");
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-01-16" );
+ s.setNextDueDate(QDate(2007, 1, 2));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-01-17" );
+ s.setNextDueDate(QDate(2007, 1, 3));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-01-18" );
+ s.setNextDueDate(QDate(2007, 1, 4));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-01-19" );
+ s.setNextDueDate(QDate(2007, 1, 5));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-01-20" );
+ s.setNextDueDate(QDate(2007, 1, 6));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-01-21" );
+ s.setNextDueDate(QDate(2007, 1, 7));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-01-22" );
+ s.setNextDueDate(QDate(2007, 1, 8));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-01-23" );
+ s.setNextDueDate(QDate(2007, 1, 9));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-01-24" );
+ s.setNextDueDate(QDate(2007, 1, 10));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-01-25" );
+ s.setNextDueDate(QDate(2007, 1, 11));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-01-26" );
+ s.setNextDueDate(QDate(2007, 1, 12));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-01-27" );
+ s.setNextDueDate(QDate(2007, 1, 13));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-01-28" );
+ s.setNextDueDate(QDate(2007, 1, 14));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-01-29" );
+ // 15 -> Last Day
+ s.setNextDueDate(QDate(2007, 1, 15));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-01-31" );
+ s.setNextDueDate(QDate(2007, 1, 16));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-02-01" );
+ s.setNextDueDate(QDate(2007, 1, 17));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-02-02" );
+ s.setNextDueDate(QDate(2007, 1, 18));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-02-03" );
+ s.setNextDueDate(QDate(2007, 1, 19));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-02-04" );
+ s.setNextDueDate(QDate(2007, 1, 20));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-02-05" );
+ s.setNextDueDate(QDate(2007, 1, 21));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-02-06" );
+ s.setNextDueDate(QDate(2007, 1, 22));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-02-07" );
+ s.setNextDueDate(QDate(2007, 1, 23));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-02-08" );
+ s.setNextDueDate(QDate(2007, 1, 24));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-02-09" );
+ s.setNextDueDate(QDate(2007, 1, 25));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-02-10" );
+ s.setNextDueDate(QDate(2007, 1, 26));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-02-11" );
+ s.setNextDueDate(QDate(2007, 1, 27));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-02-12" );
+ s.setNextDueDate(QDate(2007, 1, 28));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-02-13" );
+ s.setNextDueDate(QDate(2007, 1, 29));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-02-14" );
+ // 30th,31st -> 15th
+ s.setNextDueDate(QDate(2007, 1, 30));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-02-15" );
+ s.setNextDueDate(QDate(2007, 1, 31));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-02-15" );
+ // 30th (last day)
+ s.setNextDueDate(QDate(2007, 4, 30));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2007-05-15" );
+ // 28th of February (Last day): to 15th
+ s.setNextDueDate(QDate(1900, 2, 28));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "1900-03-15" );
+ // 28th of February (Leap year): to 13th
+ s.setNextDueDate(QDate(2000, 2, 28));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2000-03-13" );
+ // 29th of February (Leap year)
+ s.setNextDueDate(QDate(2000, 2, 29));
+ CPPUNIT_ASSERT( s.nextPayment(s.nextDueDate()).toString(format) == "2000-03-15" );
+ // Add multiple transactions
+ s.setStartDate(QDate(2007, 1, 1));
+ CPPUNIT_ASSERT( s.dateAfter(2).toString(format) == "2007-01-16" );
+ CPPUNIT_ASSERT( s.dateAfter(3).toString(format) == "2007-02-01" );
+ CPPUNIT_ASSERT( s.dateAfter(4).toString(format) == "2007-02-16" );
+ s.setStartDate(QDate(2007, 1, 12));
+ CPPUNIT_ASSERT( s.dateAfter(2).toString(format) == "2007-01-27" );
+ CPPUNIT_ASSERT( s.dateAfter(3).toString(format) == "2007-02-12" );
+ CPPUNIT_ASSERT( s.dateAfter(4).toString(format) == "2007-02-27" );
+ CPPUNIT_ASSERT( s.dateAfter(5).toString(format) == "2007-03-12" );
+ s.setStartDate(QDate(2007, 1, 13));
+ CPPUNIT_ASSERT( s.dateAfter(2).toString(format) == "2007-01-28" );
+ CPPUNIT_ASSERT( s.dateAfter(3).toString(format) == "2007-02-13" );
+ CPPUNIT_ASSERT( s.dateAfter(4).toString(format) == "2007-02-28" );
+ CPPUNIT_ASSERT( s.dateAfter(5).toString(format) == "2007-03-15" );
+ s.setStartDate(QDate(2007, 1, 14));
+ CPPUNIT_ASSERT( s.dateAfter(2).toString(format) == "2007-01-29" );
+ CPPUNIT_ASSERT( s.dateAfter(3).toString(format) == "2007-02-14" );
+ CPPUNIT_ASSERT( s.dateAfter(4).toString(format) == "2007-02-28" );
+ CPPUNIT_ASSERT( s.dateAfter(5).toString(format) == "2007-03-15" );
+ s.setStartDate(QDate(2007, 1, 15));
+ CPPUNIT_ASSERT( s.dateAfter(2).toString(format) == "2007-01-31" );
+ CPPUNIT_ASSERT( s.dateAfter(3).toString(format) == "2007-02-15" );
+ CPPUNIT_ASSERT( s.dateAfter(4).toString(format) == "2007-02-28" );
+ CPPUNIT_ASSERT( s.dateAfter(5).toString(format) == "2007-03-15" );
+ s.setStartDate(QDate(2007, 1, 16));
+ CPPUNIT_ASSERT( s.dateAfter(2).toString(format) == "2007-02-01" );
+ CPPUNIT_ASSERT( s.dateAfter(3).toString(format) == "2007-02-16" );
+ CPPUNIT_ASSERT( s.dateAfter(4).toString(format) == "2007-03-01" );
+ CPPUNIT_ASSERT( s.dateAfter(5).toString(format) == "2007-03-16" );
+ s.setStartDate(QDate(2007, 1, 27));
+ CPPUNIT_ASSERT( s.dateAfter(2).toString(format) == "2007-02-12" );
+ CPPUNIT_ASSERT( s.dateAfter(3).toString(format) == "2007-02-27" );
+ CPPUNIT_ASSERT( s.dateAfter(4).toString(format) == "2007-03-12" );
+ CPPUNIT_ASSERT( s.dateAfter(5).toString(format) == "2007-03-27" );
+ s.setStartDate(QDate(2007, 1, 28));
+ CPPUNIT_ASSERT( s.dateAfter(2).toString(format) == "2007-02-13" );
+ CPPUNIT_ASSERT( s.dateAfter(3).toString(format) == "2007-02-28" );
+ CPPUNIT_ASSERT( s.dateAfter(4).toString(format) == "2007-03-15" );
+ CPPUNIT_ASSERT( s.dateAfter(5).toString(format) == "2007-03-31" );
+ s.setStartDate(QDate(2007, 1, 29));
+ CPPUNIT_ASSERT( s.dateAfter(2).toString(format) == "2007-02-14" );
+ CPPUNIT_ASSERT( s.dateAfter(3).toString(format) == "2007-02-28" );
+ CPPUNIT_ASSERT( s.dateAfter(4).toString(format) == "2007-03-15" );
+ CPPUNIT_ASSERT( s.dateAfter(5).toString(format) == "2007-03-31" );
+ s.setStartDate(QDate(2007, 1, 30));
+ CPPUNIT_ASSERT( s.dateAfter(2).toString(format) == "2007-02-15" );
+ CPPUNIT_ASSERT( s.dateAfter(3).toString(format) == "2007-02-28" );
+ CPPUNIT_ASSERT( s.dateAfter(4).toString(format) == "2007-03-15" );
+ CPPUNIT_ASSERT( s.dateAfter(5).toString(format) == "2007-03-31" );
+ s.setStartDate(QDate(2007, 1, 31));
+ CPPUNIT_ASSERT( s.dateAfter(2).toString(format) == "2007-02-15" );
+ CPPUNIT_ASSERT( s.dateAfter(3).toString(format) == "2007-02-28" );
+ CPPUNIT_ASSERT( s.dateAfter(4).toString(format) == "2007-03-15" );
+ CPPUNIT_ASSERT( s.dateAfter(5).toString(format) == "2007-03-31" );
+ s.setStartDate(QDate(2007, 4, 29));
+ CPPUNIT_ASSERT( s.dateAfter(2).toString(format) == "2007-05-14" );
+ CPPUNIT_ASSERT( s.dateAfter(3).toString(format) == "2007-05-29" );
+ CPPUNIT_ASSERT( s.dateAfter(4).toString(format) == "2007-06-14" );
+ CPPUNIT_ASSERT( s.dateAfter(5).toString(format) == "2007-06-29" );
+ s.setStartDate(QDate(2007, 4, 30));
+ CPPUNIT_ASSERT( s.dateAfter(2).toString(format) == "2007-05-15" );
+ CPPUNIT_ASSERT( s.dateAfter(3).toString(format) == "2007-05-31" );
+ CPPUNIT_ASSERT( s.dateAfter(4).toString(format) == "2007-06-15" );
+ CPPUNIT_ASSERT( s.dateAfter(5).toString(format) == "2007-06-30" );
+}
+
+void MyMoneyScheduleTest::testPaymentDates()
+{
+ MyMoneySchedule sch;
+ QString ref_ok = QString(
+ "<!DOCTYPE TEST>\n"
+ "<SCHEDULE-CONTAINER>\n"
+
+ "<SCHEDULED_TX startDate=\"2003-12-31\" autoEnter=\"1\" weekendOption=\"0\" lastPayment=\"2006-01-31\" paymentType=\"2\" endDate=\"\" type=\"2\" id=\"SCH000032\" name=\"DSL\" fixed=\"0\" occurenceMultiplier=\"1\" occurence=\"32\" >\n"
+ " <PAYMENTS/>\n"
+ " <TRANSACTION postdate=\"2006-02-28\" memo=\"\" id=\"\" commodity=\"EUR\" entrydate=\"\" >\n"
+ " <SPLITS>\n"
+ " <SPLIT payee=\"P000076\" reconciledate=\"\" shares=\"1200/100\" action=\"Deposit\" bankid=\"\" number=\"\" reconcileflag=\"0\" memo=\"\" value=\"1200/100\" account=\"A000076\" />\n"
+ " <SPLIT payee=\"\" reconciledate=\"\" shares=\"-1200/100\" action=\"Deposit\" bankid=\"\" number=\"\" reconcileflag=\"0\" memo=\"\" value=\"-1200/100\" account=\"A000009\" />\n"
+ " </SPLITS>\n"
+ " <KEYVALUEPAIRS/>\n"
+ " </TRANSACTION>\n"
+ "</SCHEDULED_TX>\n"
+
+ "</SCHEDULE-CONTAINER>\n"
+ );
+
+ QDomDocument doc;
+ QDomElement node;
+ doc.setContent(ref_ok);
+ node = doc.documentElement().firstChild().toElement();
+
+ QDate startDate(2006,1,28);
+ QDate endDate(2006,5,30);
+
+ try {
+ sch = MyMoneySchedule(node);
+ QDate nextPayment = sch.nextPayment(startDate);
+ QValueList<QDate> list = sch.paymentDates(nextPayment, endDate);
+ CPPUNIT_ASSERT(list.count() == 3);
+ CPPUNIT_ASSERT(list[0] == QDate(2006,2,28));
+ CPPUNIT_ASSERT(list[1] == QDate(2006,3,31));
+ // Would fall on a Sunday so gets moved back to 28th.
+ CPPUNIT_ASSERT(list[2] == QDate(2006,4,28));
+
+ // Add tests for each possible occurence.
+ // Check how paymentDates is meant to work
+ // Build a list of expected dates and compare
+ // MyMoneySchedule::OCCUR_ONCE
+ sch.setOccurence(MyMoneySchedule::OCCUR_ONCE);
+ startDate.setYMD(2009,1,1);
+ endDate.setYMD(2009,12,31);
+ sch.setStartDate(startDate);
+ sch.setNextDueDate(startDate);
+ list = sch.paymentDates(startDate,endDate);
+ CPPUNIT_ASSERT(list.count() == 1);
+ CPPUNIT_ASSERT(list[0] == QDate(2009,1,1));
+ // MyMoneySchedule::OCCUR_DAILY
+ sch.setOccurence(MyMoneySchedule::OCCUR_DAILY);
+ startDate.setYMD(2009,1,1);
+ endDate.setYMD(2009,1,5);
+ sch.setStartDate(startDate);
+ sch.setNextDueDate(startDate);
+ list = sch.paymentDates(startDate,endDate);
+ CPPUNIT_ASSERT(list.count() == 5);
+ CPPUNIT_ASSERT(list[0] == QDate(2009, 1, 1));
+ CPPUNIT_ASSERT(list[1] == QDate(2009, 1, 2));
+ // Would fall on Saturday so gets moved to 2nd.
+ CPPUNIT_ASSERT(list[2] == QDate(2009, 1, 2));
+ // Would fall on Sunday so gets moved to 2nd.
+ CPPUNIT_ASSERT(list[3] == QDate(2009, 1, 2));
+ CPPUNIT_ASSERT(list[4] == QDate(2009, 1, 5));
+ // MyMoneySchedule::OCCUR_DAILY with multiplier 2
+ sch.setOccurenceMultiplier(2);
+ list = sch.paymentDates(startDate.addDays(1),endDate);
+ CPPUNIT_ASSERT(list.count() == 2);
+ // Would fall on Sunday so gets moved to 2nd.
+ CPPUNIT_ASSERT(list[0] == QDate(2009, 1, 2));
+ CPPUNIT_ASSERT(list[1] == QDate(2009, 1, 5));
+ sch.setOccurenceMultiplier(1);
+ // MyMoneySchedule::OCCUR_WEEKLY
+ sch.setOccurence(MyMoneySchedule::OCCUR_WEEKLY);
+ startDate.setYMD(2009,1,6);
+ endDate.setYMD(2009,2,4);
+ sch.setStartDate(startDate);
+ sch.setNextDueDate(startDate);
+ list = sch.paymentDates(startDate,endDate);
+ CPPUNIT_ASSERT(list.count() == 5);
+ CPPUNIT_ASSERT(list[0] == QDate(2009, 1, 6));
+ CPPUNIT_ASSERT(list[1] == QDate(2009, 1,13));
+ CPPUNIT_ASSERT(list[2] == QDate(2009, 1,20));
+ CPPUNIT_ASSERT(list[3] == QDate(2009, 1,27));
+ CPPUNIT_ASSERT(list[4] == QDate(2009, 2, 3));
+ // MyMoneySchedule::OCCUR_EVERYOTHERWEEK
+ sch.setOccurence(MyMoneySchedule::OCCUR_EVERYOTHERWEEK);
+ startDate.setYMD(2009,2,5);
+ endDate.setYMD(2009,4,3);
+ sch.setStartDate(startDate);
+ sch.setNextDueDate(startDate);
+ list = sch.paymentDates(startDate,endDate);
+ CPPUNIT_ASSERT(list.count() == 5);
+ CPPUNIT_ASSERT(list[0] == QDate(2009, 2, 5));
+ CPPUNIT_ASSERT(list[1] == QDate(2009, 2,19));
+ CPPUNIT_ASSERT(list[2] == QDate(2009, 3, 5));
+ CPPUNIT_ASSERT(list[3] == QDate(2009, 3,19));
+ CPPUNIT_ASSERT(list[4] == QDate(2009, 4, 2));
+ // MyMoneySchedule::OCCUR_FORTNIGHTLY
+ sch.setOccurence(MyMoneySchedule::OCCUR_FORTNIGHTLY);
+ startDate.setYMD(2009,4,4);
+ endDate.setYMD(2009,5,31);
+ sch.setStartDate(startDate);
+ sch.setNextDueDate(startDate);
+ list = sch.paymentDates(startDate,endDate);
+ CPPUNIT_ASSERT(list.count() == 4);
+ // First one would fall on a Saturday and would get moved
+ // to 3rd which is before start date so is not in list.
+ // Would fall on a Saturday so gets moved to 17th.
+ CPPUNIT_ASSERT(list[0] == QDate(2009, 4,17));
+ // Would fall on a Saturday so gets moved to 1st.
+ CPPUNIT_ASSERT(list[1] == QDate(2009, 5, 1));
+ // Would fall on a Saturday so gets moved to 15th.
+ CPPUNIT_ASSERT(list[2] == QDate(2009, 5,15));
+ // Would fall on a Saturday so gets moved to 29th.
+ CPPUNIT_ASSERT(list[3] == QDate(2009, 5,29));
+ // MyMoneySchedule::OCCUR_EVERYHALFMONTH
+ sch.setOccurence(MyMoneySchedule::OCCUR_EVERYHALFMONTH);
+ startDate.setYMD(2009,6,1);
+ endDate.setYMD(2009,8,11);
+ sch.setStartDate(startDate);
+ sch.setNextDueDate(startDate);
+ list = sch.paymentDates(startDate,endDate);
+ CPPUNIT_ASSERT(list.count() == 5);
+ CPPUNIT_ASSERT(list[0] == QDate(2009, 6, 1));
+ CPPUNIT_ASSERT(list[1] == QDate(2009, 6,16));
+ CPPUNIT_ASSERT(list[2] == QDate(2009, 7, 1));
+ CPPUNIT_ASSERT(list[3] == QDate(2009, 7,16));
+ // Would fall on a Saturday so gets moved to 31st.
+ CPPUNIT_ASSERT(list[4] == QDate(2009, 7, 31));
+ // MyMoneySchedule::OCCUR_EVERYTHREEWEEKS
+ sch.setOccurence(MyMoneySchedule::OCCUR_EVERYTHREEWEEKS);
+ startDate.setYMD(2009,8,12);
+ endDate.setYMD(2009,11,12);
+ sch.setStartDate(startDate);
+ sch.setNextDueDate(startDate);
+ list = sch.paymentDates(startDate,endDate);
+ CPPUNIT_ASSERT(list.count() == 5);
+ CPPUNIT_ASSERT(list[0] == QDate(2009, 8,12));
+ CPPUNIT_ASSERT(list[1] == QDate(2009, 9, 2));
+ CPPUNIT_ASSERT(list[2] == QDate(2009, 9,23));
+ CPPUNIT_ASSERT(list[3] == QDate(2009,10,14));
+ CPPUNIT_ASSERT(list[4] == QDate(2009,11, 4));
+ // MyMoneySchedule::OCCUR_EVERYFOURWEEKS
+ sch.setOccurence(MyMoneySchedule::OCCUR_EVERYFOURWEEKS);
+ startDate.setYMD(2009,11,13);
+ endDate.setYMD(2010,3,13);
+ sch.setStartDate(startDate);
+ sch.setNextDueDate(startDate);
+ list = sch.paymentDates(startDate,endDate);
+ CPPUNIT_ASSERT(list.count() == 5);
+ CPPUNIT_ASSERT(list[0] == QDate(2009,11,13));
+ CPPUNIT_ASSERT(list[1] == QDate(2009,12,11));
+ CPPUNIT_ASSERT(list[2] == QDate(2010, 1, 8));
+ CPPUNIT_ASSERT(list[3] == QDate(2010, 2, 5));
+ CPPUNIT_ASSERT(list[4] == QDate(2010, 3, 5));
+ // MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS
+ sch.setOccurence(MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS);
+ startDate.setYMD(2010,3,19);
+ endDate.setYMD(2010,7,19);
+ sch.setStartDate(startDate);
+ sch.setNextDueDate(startDate);
+ list = sch.paymentDates(startDate,endDate);
+ CPPUNIT_ASSERT(list.count() == 5);
+ CPPUNIT_ASSERT(list[0] == QDate(2010, 3,19));
+ // Would fall on a Sunday so gets moved to 16th.
+ CPPUNIT_ASSERT(list[1] == QDate(2010, 4,16));
+ CPPUNIT_ASSERT(list[2] == QDate(2010, 5,18));
+ CPPUNIT_ASSERT(list[3] == QDate(2010, 6,17));
+ // Would fall on a Saturday so gets moved to 16th.
+ CPPUNIT_ASSERT(list[4] == QDate(2010, 7,16));
+ // MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS
+ sch.setOccurence(MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS);
+ startDate.setYMD(2010,7,26);
+ endDate.setYMD(2011,3,26);
+ sch.setStartDate(startDate);
+ sch.setNextDueDate(startDate);
+ list = sch.paymentDates(startDate,endDate);
+ CPPUNIT_ASSERT(list.count() == 5);
+ CPPUNIT_ASSERT(list[0] == QDate(2010, 7,26));
+ CPPUNIT_ASSERT(list[1] == QDate(2010, 9,20));
+ CPPUNIT_ASSERT(list[2] == QDate(2010,11,15));
+ CPPUNIT_ASSERT(list[3] == QDate(2011, 1,10));
+ CPPUNIT_ASSERT(list[4] == QDate(2011, 3, 7));
+ // MyMoneySchedule::OCCUR_EVERYOTHERMONTH
+ sch.setOccurence(MyMoneySchedule::OCCUR_EVERYOTHERMONTH);
+ startDate.setYMD(2011,3,14);
+ endDate.setYMD(2011,11,20);
+ sch.setStartDate(startDate);
+ sch.setNextDueDate(startDate);
+ list = sch.paymentDates(startDate,endDate);
+ CPPUNIT_ASSERT(list.count() == 5);
+ CPPUNIT_ASSERT(list[0] == QDate(2011, 3,14));
+ // Would fall on a Saturday so gets moved to 13th.
+ CPPUNIT_ASSERT(list[1] == QDate(2011, 5,13));
+ CPPUNIT_ASSERT(list[2] == QDate(2011, 7,14));
+ CPPUNIT_ASSERT(list[3] == QDate(2011, 9,14));
+ CPPUNIT_ASSERT(list[4] == QDate(2011,11,14));
+ // MyMoneySchedule::OCCUR_EVERYTHREEMONTHS
+ sch.setOccurence(MyMoneySchedule::OCCUR_EVERYTHREEMONTHS);
+ startDate.setYMD(2011,11,15);
+ endDate.setYMD(2012,11,19);
+ sch.setStartDate(startDate);
+ sch.setNextDueDate(startDate);
+ list = sch.paymentDates(startDate,endDate);
+ CPPUNIT_ASSERT(list.count() == 5);
+ CPPUNIT_ASSERT(list[0] == QDate(2011,11,15));
+ CPPUNIT_ASSERT(list[1] == QDate(2012, 2,15));
+ CPPUNIT_ASSERT(list[2] == QDate(2012, 5,15));
+ CPPUNIT_ASSERT(list[3] == QDate(2012, 8,15));
+ CPPUNIT_ASSERT(list[4] == QDate(2012,11,15));
+ // MyMoneySchedule::OCCUR_QUARTERLY
+ sch.setOccurence(MyMoneySchedule::OCCUR_QUARTERLY);
+ startDate.setYMD(2012,11,20);
+ endDate.setYMD(2013,11,23);
+ sch.setStartDate(startDate);
+ sch.setNextDueDate(startDate);
+ list = sch.paymentDates(startDate,endDate);
+ CPPUNIT_ASSERT(list.count() == 5);
+ CPPUNIT_ASSERT(list[0] == QDate(2012,11,20));
+ CPPUNIT_ASSERT(list[1] == QDate(2013, 2,20));
+ CPPUNIT_ASSERT(list[2] == QDate(2013, 5,20));
+ CPPUNIT_ASSERT(list[3] == QDate(2013, 8,20));
+ CPPUNIT_ASSERT(list[4] == QDate(2013,11,20));
+ // MyMoneySchedule::OCCUR_EVERYFOURMONTHS
+ sch.setOccurence(MyMoneySchedule::OCCUR_EVERYFOURMONTHS);
+ startDate.setYMD(2013,11,21);
+ endDate.setYMD(2015, 3,23);
+ sch.setStartDate(startDate);
+ sch.setNextDueDate(startDate);
+ list = sch.paymentDates(startDate,endDate);
+ CPPUNIT_ASSERT(list.count() == 5);
+ CPPUNIT_ASSERT(list[0] == QDate(2013,11,21));
+ CPPUNIT_ASSERT(list[1] == QDate(2014, 3,21));
+ CPPUNIT_ASSERT(list[2] == QDate(2014, 7,21));
+ CPPUNIT_ASSERT(list[3] == QDate(2014,11,21));
+ // Would fall on a Saturday so gets moved to 20th.
+ CPPUNIT_ASSERT(list[4] == QDate(2015, 3,20));
+ // MyMoneySchedule::OCCUR_TWICEYEARLY
+ sch.setOccurence(MyMoneySchedule::OCCUR_TWICEYEARLY);
+ startDate.setYMD(2015, 3,22);
+ endDate.setYMD(2017, 3,29);
+ sch.setStartDate(startDate);
+ sch.setNextDueDate(startDate);
+ list = sch.paymentDates(startDate,endDate);
+ CPPUNIT_ASSERT(list.count() == 4);
+ // First date would fall on a Sunday which would get moved
+ // to 20th which is before start date so not in list.
+ CPPUNIT_ASSERT(list[0] == QDate(2015, 9,22));
+ CPPUNIT_ASSERT(list[1] == QDate(2016, 3,22));
+ CPPUNIT_ASSERT(list[2] == QDate(2016, 9,22));
+ CPPUNIT_ASSERT(list[3] == QDate(2017, 3,22));
+ // MyMoneySchedule::OCCUR_YEARLY
+ sch.setOccurence(MyMoneySchedule::OCCUR_YEARLY);
+ startDate.setYMD(2017, 3,23);
+ endDate.setYMD(2021, 3,29);
+ sch.setStartDate(startDate);
+ sch.setNextDueDate(startDate);
+ list = sch.paymentDates(startDate,endDate);
+ CPPUNIT_ASSERT(list.count() == 5);
+ CPPUNIT_ASSERT(list[0] == QDate(2017, 3,23));
+ CPPUNIT_ASSERT(list[1] == QDate(2018, 3,23));
+ // Would fall on a Saturday so gets moved to 22nd.
+ CPPUNIT_ASSERT(list[2] == QDate(2019, 3,22));
+ CPPUNIT_ASSERT(list[3] == QDate(2020, 3,23));
+ CPPUNIT_ASSERT(list[4] == QDate(2021, 3,23));
+ // MyMoneySchedule::OCCUR_EVERYOTHERYEAR
+ sch.setOccurence(MyMoneySchedule::OCCUR_EVERYOTHERYEAR);
+ startDate.setYMD(2021, 3,24);
+ endDate.setYMD(2029, 3,30);
+ sch.setStartDate(startDate);
+ sch.setNextDueDate(startDate);
+ list = sch.paymentDates(startDate,endDate);
+ CPPUNIT_ASSERT(list.count() == 5);
+ CPPUNIT_ASSERT(list[0] == QDate(2021, 3,24));
+ CPPUNIT_ASSERT(list[1] == QDate(2023, 3,24));
+ CPPUNIT_ASSERT(list[2] == QDate(2025, 3,24));
+ CPPUNIT_ASSERT(list[3] == QDate(2027, 3,24));
+ // Would fall on a Saturday so gets moved to 23rd.
+ CPPUNIT_ASSERT(list[4] == QDate(2029, 3,23));
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+/*
+ MyMoneyScheduled *m = MyMoneyScheduled::instance();
+ CPPUNIT_ASSERT(m!=NULL);
+
+ try
+ {
+ MyMoneySchedule s1 = m->getSchedule("A000001", "SCHED00001");
+ MyMoneySchedule s2 = m->getSchedule("A000001", "SCHED00002");
+ MyMoneySchedule s3 = m->getSchedule("A000001", "SCHED00003");
+
+ QValueList<QDate> payments1 = s1.paymentDates(QDate(2001, 1, 1), QDate(2001, 2, 1));
+ QValueList<QDate> payments2 = s2.paymentDates(QDate(2001, 2, 1), QDate(2001, 6, 1));
+ QValueList<QDate> payments3 = s3.paymentDates(QDate(2001, 3, 1), QDate(2005, 3, 1));
+
+ CPPUNIT_ASSERT(payments1.size() == 5);
+ CPPUNIT_ASSERT(payments2.size() == 5);
+ CPPUNIT_ASSERT(payments3.size() == 5);
+ } catch (MyMoneyException *e)
+ {
+ CPPUNIT_FAIL("Unexpected exception");
+ delete e;
+ }
+*/
+}
+
+void MyMoneyScheduleTest::testReplaceSchedule()
+{
+/*
+ MyMoneyScheduled *m = MyMoneyScheduled::instance();
+ CPPUNIT_ASSERT(m!=NULL);
+
+ try
+ {
+ MyMoneySchedule s = m->getSchedule("A000001", "SCHED00002");
+ CPPUNIT_ASSERT(s.type() == MyMoneySchedule::TYPE_DEPOSIT);
+ s.setType(MyMoneySchedule::TYPE_TRANSFER);
+ m->replaceSchedule("A000001", "SCHED00002", s);
+ s = m->getSchedule("A000001", "SCHED00002");
+ CPPUNIT_ASSERT(s.type() == MyMoneySchedule::TYPE_TRANSFER);
+
+ } catch(MyMoneyException *e) {
+ char buf[256];
+ sprintf(buf, "Unexpected exception: %s", e->what().latin1());
+ CPPUNIT_FAIL(buf);
+ delete e;
+ }
+*/
+}
+
+void MyMoneyScheduleTest::testRemoveSchedule()
+{
+/*
+ MyMoneyScheduled *m = MyMoneyScheduled::instance();
+ CPPUNIT_ASSERT(m!=NULL);
+
+ try
+ {
+ QValueList<QString> testList;
+
+ testList = m->getScheduled("A000001");
+ CPPUNIT_ASSERT(testList.size() == 3);
+
+ m->removeSchedule("A000001", "SCHED00002");
+
+ testList = m->getScheduled("A000001");
+ CPPUNIT_ASSERT(testList.size() == 2);
+
+ m->getSchedule("A000001", "SCHED00002");
+
+ CPPUNIT_FAIL("Exception expected while getting schedule SCHED00002");
+ } catch (MyMoneyException *e)
+ {
+ delete e;
+ }
+*/
+}
+
+void MyMoneyScheduleTest::testWriteXML() {
+ MyMoneySchedule sch( "A Name",
+ MyMoneySchedule::TYPE_BILL,
+ MyMoneySchedule::OCCUR_WEEKLY, 123,
+ MyMoneySchedule::STYPE_DIRECTDEBIT,
+ QDate::currentDate(),
+ QDate(),
+ true,
+ true);
+
+ sch.setLastPayment(QDate::currentDate());
+ sch.recordPayment(QDate::currentDate());
+ sch.setId("SCH0001");
+
+ MyMoneyTransaction t;
+ t.setPostDate(QDate(2001,12,28));
+ t.setEntryDate(QDate(2003,9,29));
+ t.setId("T000000000000000001");
+ t.setMemo("Wohnung:Miete");
+ t.setCommodity("EUR");
+ t.setValue("key", "value");
+
+ MyMoneySplit s;
+ s.setPayeeId("P000001");
+ s.setShares(MyMoneyMoney(96379, 100));
+ s.setValue(MyMoneyMoney(96379, 100));
+ s.setAccountId("A000076");
+ s.setBankID("SPID1");
+ s.setReconcileFlag(MyMoneySplit::Reconciled);
+ t.addSplit(s);
+
+ s.setPayeeId("P000001");
+ s.setShares(MyMoneyMoney(-96379, 100));
+ s.setValue(MyMoneyMoney(-96379, 100));
+ s.setAccountId("A000276");
+ s.setBankID("SPID2");
+ s.setReconcileFlag(MyMoneySplit::Cleared);
+ s.clearId();
+ t.addSplit(s);
+
+ sch.setTransaction(t);
+
+ QDomDocument doc("TEST");
+ QDomElement el = doc.createElement("SCHEDULE-CONTAINER");
+ doc.appendChild(el);
+ sch.writeXML(doc, el);
+
+ QString ref = QString(
+ "<!DOCTYPE TEST>\n"
+ "<SCHEDULE-CONTAINER>\n"
+ " <SCHEDULED_TX startDate=\"%1\" autoEnter=\"1\" weekendOption=\"2\" lastPayment=\"%2\" paymentType=\"1\" endDate=\"\" type=\"1\" id=\"SCH0001\" name=\"A Name\" fixed=\"1\" occurenceMultiplier=\"123\" occurence=\"4\" >\n"
+ " <PAYMENTS>\n"
+ " <PAYMENT date=\"%3\" />\n"
+ " </PAYMENTS>\n"
+ " <TRANSACTION postdate=\"2001-12-28\" memo=\"Wohnung:Miete\" id=\"\" commodity=\"EUR\" entrydate=\"2003-09-29\" >\n"
+ " <SPLITS>\n"
+ " <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"96379/100\" action=\"\" bankid=\"\" number=\"\" reconcileflag=\"2\" memo=\"\" value=\"96379/100\" id=\"S0001\" account=\"A000076\" />\n"
+ " <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"-96379/100\" action=\"\" bankid=\"\" number=\"\" reconcileflag=\"1\" memo=\"\" value=\"-96379/100\" id=\"S0002\" account=\"A000276\" />\n"
+ " </SPLITS>\n"
+ " <KEYVALUEPAIRS>\n"
+ " <PAIR key=\"key\" value=\"value\" />\n"
+ " </KEYVALUEPAIRS>\n"
+ " </TRANSACTION>\n"
+ " </SCHEDULED_TX>\n"
+ "</SCHEDULE-CONTAINER>\n"
+ ).arg(QDate::currentDate().toString(Qt::ISODate))
+ .arg(QDate::currentDate().toString(Qt::ISODate))
+ .arg(QDate::currentDate().toString(Qt::ISODate));
+
+ CPPUNIT_ASSERT(doc.toString() == ref);
+}
+
+void MyMoneyScheduleTest::testReadXML() {
+ MyMoneySchedule sch;
+
+ QString ref_ok1 = QString(
+ "<!DOCTYPE TEST>\n"
+ "<SCHEDULE-CONTAINER>\n"
+ " <SCHEDULED_TX startDate=\"%1\" autoEnter=\"1\" weekendOption=\"2\" lastPayment=\"%2\" paymentType=\"1\" endDate=\"\" type=\"1\" id=\"SCH0002\" name=\"A Name\" fixed=\"1\" occurenceMultiplier=\"1\" occurence=\"4\" >\n"
+ " <PAYMENTS>\n"
+ " <PAYMENT date=\"%3\" />\n"
+ " </PAYMENTS>\n"
+ " <TRANSACTION postdate=\"2001-12-28\" memo=\"Wohnung:Miete\" id=\"\" commodity=\"EUR\" entrydate=\"2003-09-29\" >\n"
+ " <SPLITS>\n"
+ " <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"96379/100\" action=\"\" bankid=\"SPID1\" number=\"\" reconcileflag=\"2\" memo=\"\" value=\"96379/100\" account=\"A000076\" />\n"
+ " <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"-96379/100\" action=\"\" bankid=\"SPID2\" number=\"\" reconcileflag=\"1\" memo=\"\" value=\"-96379/100\" account=\"A000276\" />\n"
+ " </SPLITS>\n"
+ " <KEYVALUEPAIRS>\n"
+ " <PAIR key=\"key\" value=\"value\" />\n"
+ " </KEYVALUEPAIRS>\n"
+ " </TRANSACTION>\n"
+ " </SCHEDULED_TX>\n"
+ "</SCHEDULE-CONTAINER>\n"
+ ).arg(QDate::currentDate().toString(Qt::ISODate))
+ .arg(QDate::currentDate().toString(Qt::ISODate))
+ .arg(QDate::currentDate().toString(Qt::ISODate));
+
+ // diff to ref_ok1 is that we now have an empty entrydate
+ // in the transaction parameters
+ QString ref_ok2 = QString(
+ "<!DOCTYPE TEST>\n"
+ "<SCHEDULE-CONTAINER>\n"
+ " <SCHEDULED_TX startDate=\"%1\" autoEnter=\"1\" weekendOption=\"2\" lastPayment=\"%2\" paymentType=\"1\" endDate=\"\" type=\"1\" id=\"SCH0002\" name=\"A Name\" fixed=\"1\" occurenceMultiplier=\"1\" occurence=\"4\" >\n"
+ " <PAYMENTS>\n"
+ " <PAYMENT date=\"%3\" />\n"
+ " </PAYMENTS>\n"
+ " <TRANSACTION postdate=\"2001-12-28\" memo=\"Wohnung:Miete\" id=\"\" commodity=\"EUR\" entrydate=\"\" >\n"
+ " <SPLITS>\n"
+ " <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"96379/100\" action=\"\" bankid=\"SPID1\" number=\"\" reconcileflag=\"2\" memo=\"\" value=\"96379/100\" account=\"A000076\" />\n"
+ " <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"-96379/100\" action=\"\" bankid=\"SPID2\" number=\"\" reconcileflag=\"1\" memo=\"\" value=\"-96379/100\" account=\"A000276\" />\n"
+ " </SPLITS>\n"
+ " <KEYVALUEPAIRS>\n"
+ " <PAIR key=\"key\" value=\"value\" />\n"
+ " </KEYVALUEPAIRS>\n"
+ " </TRANSACTION>\n"
+ " </SCHEDULED_TX>\n"
+ "</SCHEDULE-CONTAINER>\n"
+ ).arg(QDate::currentDate().toString(Qt::ISODate))
+ .arg(QDate::currentDate().toString(Qt::ISODate))
+ .arg(QDate::currentDate().toString(Qt::ISODate));
+
+ QString ref_false = QString(
+ "<!DOCTYPE TEST>\n"
+ "<SCHEDULE-CONTAINER>\n"
+ " <SCHEDULE startDate=\"%1\" autoEnter=\"1\" weekendOption=\"2\" lastPayment=\"%2\" paymentType=\"1\" endDate=\"\" type=\"1\" id=\"SCH0002\" name=\"A Name\" fixed=\"1\" occurenceMultiplier=\"1\" occurence=\"4\" >\n"
+ " <PAYMENTS count=\"1\" >\n"
+ " <PAYMENT date=\"%3\" />\n"
+ " </PAYMENTS>\n"
+ " <TRANSACTION postdate=\"2001-12-28\" memo=\"Wohnung:Miete\" id=\"\" commodity=\"EUR\" entrydate=\"2003-09-29\" >\n"
+ " <SPLITS>\n"
+ " <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"96379/100\" action=\"\" bankid=\"SPID1\" number=\"\" reconcileflag=\"2\" memo=\"\" value=\"96379/100\" account=\"A000076\" />\n"
+ " <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"-96379/100\" action=\"\" bankid=\"SPID2\" number=\"\" reconcileflag=\"1\" memo=\"\" value=\"-96379/100\" account=\"A000276\" />\n"
+ " </SPLITS>\n"
+ " <KEYVALUEPAIRS>\n"
+ " <PAIR key=\"key\" value=\"value\" />\n"
+ " </KEYVALUEPAIRS>\n"
+ " </TRANSACTION>\n"
+ " </SCHEDULED_TX>\n"
+ "</SCHEDULE-CONTAINER>\n"
+ ).arg(QDate::currentDate().toString(Qt::ISODate))
+ .arg(QDate::currentDate().toString(Qt::ISODate))
+ .arg(QDate::currentDate().toString(Qt::ISODate));
+
+ QDomDocument doc;
+ QDomElement node;
+ doc.setContent(ref_false);
+ node = doc.documentElement().firstChild().toElement();
+
+ try {
+ sch = MyMoneySchedule(node);
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+
+ doc.setContent(ref_ok1);
+ node = doc.documentElement().firstChild().toElement();
+
+
+ try {
+ sch = MyMoneySchedule(node);
+ CPPUNIT_ASSERT(sch.id() == "SCH0002");
+ CPPUNIT_ASSERT(sch.nextDueDate() == QDate::currentDate().addDays(7));
+ CPPUNIT_ASSERT(sch.startDate() == QDate::currentDate());
+ CPPUNIT_ASSERT(sch.endDate() == QDate());
+ CPPUNIT_ASSERT(sch.autoEnter() == true);
+ CPPUNIT_ASSERT(sch.isFixed() == true);
+ CPPUNIT_ASSERT(sch.weekendOption() == MyMoneySchedule::MoveNothing);
+ CPPUNIT_ASSERT(sch.lastPayment() == QDate::currentDate());
+ CPPUNIT_ASSERT(sch.paymentType() == MyMoneySchedule::STYPE_DIRECTDEBIT);
+ CPPUNIT_ASSERT(sch.type() == MyMoneySchedule::TYPE_BILL);
+ CPPUNIT_ASSERT(sch.name() == "A Name");
+ CPPUNIT_ASSERT(sch.occurence() == MyMoneySchedule::OCCUR_WEEKLY);
+ CPPUNIT_ASSERT(sch.occurenceMultiplier() == 1);
+ CPPUNIT_ASSERT(sch.nextDueDate() == sch.lastPayment().addDays(7));
+ CPPUNIT_ASSERT(sch.recordedPayments().count() == 1);
+ CPPUNIT_ASSERT(sch.recordedPayments()[0] == QDate::currentDate());
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ doc.setContent(ref_ok2);
+ node = doc.documentElement().firstChild().toElement();
+
+
+ try {
+ sch = MyMoneySchedule(node);
+ CPPUNIT_ASSERT(sch.id() == "SCH0002");
+ CPPUNIT_ASSERT(sch.nextDueDate() == QDate::currentDate().addDays(7));
+ CPPUNIT_ASSERT(sch.startDate() == QDate::currentDate());
+ CPPUNIT_ASSERT(sch.endDate() == QDate());
+ CPPUNIT_ASSERT(sch.autoEnter() == true);
+ CPPUNIT_ASSERT(sch.isFixed() == true);
+ CPPUNIT_ASSERT(sch.weekendOption() == MyMoneySchedule::MoveNothing);
+ CPPUNIT_ASSERT(sch.lastPayment() == QDate::currentDate());
+ CPPUNIT_ASSERT(sch.paymentType() == MyMoneySchedule::STYPE_DIRECTDEBIT);
+ CPPUNIT_ASSERT(sch.type() == MyMoneySchedule::TYPE_BILL);
+ CPPUNIT_ASSERT(sch.name() == "A Name");
+ CPPUNIT_ASSERT(sch.occurence() == MyMoneySchedule::OCCUR_WEEKLY);
+ CPPUNIT_ASSERT(sch.occurenceMultiplier() == 1);
+ CPPUNIT_ASSERT(sch.nextDueDate() == sch.lastPayment().addDays(7));
+ CPPUNIT_ASSERT(sch.recordedPayments().count() == 1);
+ CPPUNIT_ASSERT(sch.recordedPayments()[0] == QDate::currentDate());
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneyScheduleTest::testHasReferenceTo()
+{
+ MyMoneySchedule sch;
+ QString ref_ok = QString(
+ "<!DOCTYPE TEST>\n"
+ "<SCHEDULE-CONTAINER>\n"
+ " <SCHEDULED_TX startDate=\"%1\" autoEnter=\"1\" weekendOption=\"2\" lastPayment=\"%2\" paymentType=\"1\" endDate=\"\" type=\"1\" id=\"SCH0002\" name=\"A Name\" fixed=\"1\" occurenceMultiplier=\"1\" occurence=\"4\" >\n"
+ " <PAYMENTS>\n"
+ " <PAYMENT date=\"%3\" />\n"
+ " </PAYMENTS>\n"
+ " <TRANSACTION postdate=\"2001-12-28\" memo=\"Wohnung:Miete\" id=\"\" commodity=\"EUR\" entrydate=\"2003-09-29\" >\n"
+ " <SPLITS>\n"
+ " <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"96379/100\" action=\"\" number=\"\" reconcileflag=\"2\" memo=\"\" value=\"96379/100\" account=\"A000076\" />\n"
+ " <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"-96379/100\" action=\"\" number=\"\" reconcileflag=\"1\" memo=\"\" value=\"-96379/100\" account=\"A000276\" />\n"
+ " </SPLITS>\n"
+ " <KEYVALUEPAIRS>\n"
+ " <PAIR key=\"key\" value=\"value\" />\n"
+ " </KEYVALUEPAIRS>\n"
+ " </TRANSACTION>\n"
+ " </SCHEDULED_TX>\n"
+ "</SCHEDULE-CONTAINER>\n"
+ ).arg(QDate::currentDate().toString(Qt::ISODate))
+ .arg(QDate::currentDate().toString(Qt::ISODate))
+ .arg(QDate::currentDate().toString(Qt::ISODate));
+
+ QDomDocument doc;
+ QDomElement node;
+ doc.setContent(ref_ok);
+ node = doc.documentElement().firstChild().toElement();
+
+ try {
+ sch = MyMoneySchedule(node);
+
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ CPPUNIT_ASSERT(sch.hasReferenceTo("P000001") == true);
+ CPPUNIT_ASSERT(sch.hasReferenceTo("A000276") == true);
+ CPPUNIT_ASSERT(sch.hasReferenceTo("A000076") == true);
+ CPPUNIT_ASSERT(sch.hasReferenceTo("EUR") == true);
+}
+
+void MyMoneyScheduleTest::testAdjustedNextDueDate()
+{
+ MyMoneySchedule s;
+
+ QDate dueDate(2007,9,3); // start on a monday
+ for(int i = 0; i < 7; ++i) {
+ s.setNextDueDate(dueDate);
+ s.setWeekendOption(MyMoneySchedule::MoveNothing);
+ CPPUNIT_ASSERT(s.adjustedNextDueDate() == dueDate);
+
+ s.setWeekendOption(MyMoneySchedule::MoveFriday);
+ switch(i) {
+ case 5: // saturday
+ case 6: // sunday
+ break;
+ CPPUNIT_ASSERT(s.adjustedNextDueDate() == QDate(2007,9,7));
+ default:
+ CPPUNIT_ASSERT(s.adjustedNextDueDate() == dueDate);
+ break;
+ }
+
+ s.setWeekendOption(MyMoneySchedule::MoveMonday);
+ switch(i) {
+ case 5: // saturday
+ case 6: // sunday
+ CPPUNIT_ASSERT(s.adjustedNextDueDate() == QDate(2007,9,10));
+ break;
+ default:
+ CPPUNIT_ASSERT(s.adjustedNextDueDate() == dueDate);
+ break;
+ }
+ dueDate = dueDate.addDays(1);
+ }
+}
+
+void MyMoneyScheduleTest::testModifyNextDueDate(void)
+{
+ MyMoneySchedule s;
+ s.setStartDate(QDate(2007, 1, 1));
+ s.setOccurence(MyMoneySchedule::OCCUR_MONTHLY);
+ s.setNextDueDate(s.startDate().addMonths(1));
+ s.setLastPayment(s.startDate());
+
+ QValueList<QDate> dates;
+ dates = s.paymentDates(QDate(2007,2,1), QDate(2007,2,1));
+ CPPUNIT_ASSERT(s.nextDueDate() == QDate(2007,2,1));
+ CPPUNIT_ASSERT(dates.count() == 1);
+ CPPUNIT_ASSERT(dates[0] == QDate(2007,2,1));
+
+ s.setNextDueDate(QDate(2007,1,24));
+
+ dates = s.paymentDates(QDate(2007,2,1), QDate(2007,2,1));
+ CPPUNIT_ASSERT(s.nextDueDate() == QDate(2007,1,24));
+ CPPUNIT_ASSERT(dates.count() == 0);
+
+ dates = s.paymentDates(QDate(2007,1,24), QDate(2007,1,24));
+ CPPUNIT_ASSERT(dates.count() == 1);
+
+ dates = s.paymentDates(QDate(2007,1,24), QDate(2007,2,24));
+ CPPUNIT_ASSERT(dates.count() == 2);
+ CPPUNIT_ASSERT(dates[0] == QDate(2007,1,24));
+ CPPUNIT_ASSERT(dates[1] == QDate(2007,2,24));
+
+}
+
+void MyMoneyScheduleTest::testDaysBetweenEvents()
+{
+ CPPUNIT_ASSERT(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_ONCE) == 0);
+ CPPUNIT_ASSERT(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_DAILY) == 1);
+ CPPUNIT_ASSERT(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_WEEKLY) == 7);
+ CPPUNIT_ASSERT(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_EVERYOTHERWEEK) == 14);
+ CPPUNIT_ASSERT(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_FORTNIGHTLY) == 14);
+ CPPUNIT_ASSERT(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_EVERYHALFMONTH) == 15);
+ CPPUNIT_ASSERT(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_EVERYTHREEWEEKS) == 21);
+ CPPUNIT_ASSERT(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_EVERYFOURWEEKS) == 28);
+ CPPUNIT_ASSERT(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS) == 30);
+ CPPUNIT_ASSERT(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_MONTHLY) == 30);
+ CPPUNIT_ASSERT(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS) == 56);
+ CPPUNIT_ASSERT(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_EVERYOTHERMONTH) == 60);
+ CPPUNIT_ASSERT(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_EVERYTHREEMONTHS) == 90);
+ CPPUNIT_ASSERT(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_QUARTERLY) == 90);
+ CPPUNIT_ASSERT(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_EVERYFOURMONTHS) == 120);
+ CPPUNIT_ASSERT(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_TWICEYEARLY) == 180);
+ CPPUNIT_ASSERT(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_YEARLY) == 360);
+ CPPUNIT_ASSERT(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_EVERYOTHERYEAR) == 0);
+}
+
+void MyMoneyScheduleTest::testStringToOccurence()
+{
+ // For each occurenceE:
+ // test MyMoneySchedule::stringToOccurence(QString) == occurence
+ CPPUNIT_ASSERT( MyMoneySchedule::stringToOccurence(i18n("Once")) == MyMoneySchedule::OCCUR_ONCE );
+ CPPUNIT_ASSERT( MyMoneySchedule::stringToOccurence(i18n("Daily")) == MyMoneySchedule::OCCUR_DAILY );
+ CPPUNIT_ASSERT( MyMoneySchedule::stringToOccurence(i18n("Weekly")) == MyMoneySchedule::OCCUR_WEEKLY );
+ CPPUNIT_ASSERT( MyMoneySchedule::stringToOccurence(i18n("Every other week")) == MyMoneySchedule::OCCUR_EVERYOTHERWEEK );
+ CPPUNIT_ASSERT( MyMoneySchedule::stringToOccurence(i18n("Fortnightly")) == MyMoneySchedule::OCCUR_FORTNIGHTLY );
+ CPPUNIT_ASSERT( MyMoneySchedule::stringToOccurence(i18n("Every half month")) == MyMoneySchedule::OCCUR_EVERYHALFMONTH );
+ CPPUNIT_ASSERT( MyMoneySchedule::stringToOccurence(i18n("Every four weeks")) == MyMoneySchedule::OCCUR_EVERYFOURWEEKS );
+ CPPUNIT_ASSERT( MyMoneySchedule::stringToOccurence(i18n("Monthly")) == MyMoneySchedule::OCCUR_MONTHLY );
+ CPPUNIT_ASSERT( MyMoneySchedule::stringToOccurence(i18n("Every eight weeks")) == MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS );
+ CPPUNIT_ASSERT( MyMoneySchedule::stringToOccurence(i18n("Every two months")) == MyMoneySchedule::OCCUR_EVERYOTHERMONTH );
+ CPPUNIT_ASSERT( MyMoneySchedule::stringToOccurence(i18n("Every three months")) == MyMoneySchedule::OCCUR_EVERYTHREEMONTHS );
+ CPPUNIT_ASSERT( MyMoneySchedule::stringToOccurence(i18n("Quarterly")) == MyMoneySchedule::OCCUR_QUARTERLY );
+ CPPUNIT_ASSERT( MyMoneySchedule::stringToOccurence(i18n("Every four months")) == MyMoneySchedule::OCCUR_EVERYFOURMONTHS );
+ CPPUNIT_ASSERT( MyMoneySchedule::stringToOccurence(i18n("Twice yearly")) == MyMoneySchedule::OCCUR_TWICEYEARLY );
+ CPPUNIT_ASSERT( MyMoneySchedule::stringToOccurence(i18n("Yearly")) == MyMoneySchedule::OCCUR_YEARLY );
+ CPPUNIT_ASSERT( MyMoneySchedule::stringToOccurence(i18n("Every other year")) == MyMoneySchedule::OCCUR_EVERYOTHERYEAR );
+ // test occurence == stringToOccurence(i18n(occurenceToString(occurence)))
+ CPPUNIT_ASSERT( MyMoneySchedule::OCCUR_ONCE == MyMoneySchedule::stringToOccurence(i18n(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_ONCE))) );
+ CPPUNIT_ASSERT( MyMoneySchedule::OCCUR_DAILY == MyMoneySchedule::stringToOccurence(i18n(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_DAILY))) );
+ CPPUNIT_ASSERT( MyMoneySchedule::OCCUR_WEEKLY == MyMoneySchedule::stringToOccurence(i18n(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_WEEKLY))) );
+ CPPUNIT_ASSERT( MyMoneySchedule::OCCUR_EVERYOTHERWEEK == MyMoneySchedule::stringToOccurence(i18n(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYOTHERWEEK))) );
+ CPPUNIT_ASSERT( MyMoneySchedule::OCCUR_FORTNIGHTLY == MyMoneySchedule::stringToOccurence(i18n(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_FORTNIGHTLY))) );
+ CPPUNIT_ASSERT( MyMoneySchedule::OCCUR_EVERYHALFMONTH == MyMoneySchedule::stringToOccurence(i18n(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYHALFMONTH))) );
+ CPPUNIT_ASSERT( MyMoneySchedule::OCCUR_EVERYTHREEWEEKS == MyMoneySchedule::stringToOccurence(i18n(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYTHREEWEEKS))) );
+ CPPUNIT_ASSERT( MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS == MyMoneySchedule::stringToOccurence(i18n(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS))) );
+ CPPUNIT_ASSERT( MyMoneySchedule::OCCUR_EVERYTHREEWEEKS == MyMoneySchedule::stringToOccurence(i18n(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYTHREEWEEKS))) );
+ CPPUNIT_ASSERT( MyMoneySchedule::OCCUR_EVERYFOURWEEKS == MyMoneySchedule::stringToOccurence(i18n(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYFOURWEEKS))) );
+ CPPUNIT_ASSERT( MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS == MyMoneySchedule::stringToOccurence(i18n(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS))) );
+ CPPUNIT_ASSERT( MyMoneySchedule::OCCUR_MONTHLY == MyMoneySchedule::stringToOccurence(i18n(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_MONTHLY))) );
+ CPPUNIT_ASSERT( MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS == MyMoneySchedule::stringToOccurence(i18n(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS))) );
+ CPPUNIT_ASSERT( MyMoneySchedule::OCCUR_EVERYOTHERMONTH == MyMoneySchedule::stringToOccurence(i18n(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYOTHERMONTH))) );
+ CPPUNIT_ASSERT( MyMoneySchedule::OCCUR_EVERYTHREEMONTHS == MyMoneySchedule::stringToOccurence(i18n(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYTHREEMONTHS))) );
+ CPPUNIT_ASSERT( MyMoneySchedule::OCCUR_QUARTERLY == MyMoneySchedule::stringToOccurence(i18n(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_QUARTERLY))) );
+ CPPUNIT_ASSERT( MyMoneySchedule::OCCUR_EVERYFOURMONTHS == MyMoneySchedule::stringToOccurence(i18n(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYFOURMONTHS))) );
+ CPPUNIT_ASSERT( MyMoneySchedule::OCCUR_TWICEYEARLY == MyMoneySchedule::stringToOccurence(i18n(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_TWICEYEARLY))) );
+ CPPUNIT_ASSERT( MyMoneySchedule::OCCUR_YEARLY == MyMoneySchedule::stringToOccurence(i18n(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_YEARLY))) );
+ CPPUNIT_ASSERT( MyMoneySchedule::OCCUR_EVERYOTHERYEAR == MyMoneySchedule::stringToOccurence(i18n(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYOTHERYEAR))) );
+}
+void MyMoneyScheduleTest::testEventsPerYear()
+{
+ CPPUNIT_ASSERT(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_ONCE) == 0);
+ CPPUNIT_ASSERT(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_DAILY) == 365);
+ CPPUNIT_ASSERT(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_WEEKLY) == 52);
+ CPPUNIT_ASSERT(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_EVERYOTHERWEEK) == 26);
+ CPPUNIT_ASSERT(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_FORTNIGHTLY) == 26);
+ CPPUNIT_ASSERT(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_EVERYHALFMONTH) == 24);
+ CPPUNIT_ASSERT(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_EVERYTHREEWEEKS) == 17);
+ CPPUNIT_ASSERT(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_EVERYFOURWEEKS) == 13);
+ CPPUNIT_ASSERT(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS) == 12);
+ CPPUNIT_ASSERT(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_MONTHLY) == 12);
+ CPPUNIT_ASSERT(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS) == 6);
+ CPPUNIT_ASSERT(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_EVERYOTHERMONTH) == 6);
+ CPPUNIT_ASSERT(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_EVERYTHREEMONTHS) == 4);
+ CPPUNIT_ASSERT(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_QUARTERLY) == 4);
+ CPPUNIT_ASSERT(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_EVERYFOURMONTHS) == 3);
+ CPPUNIT_ASSERT(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_TWICEYEARLY) == 2);
+ CPPUNIT_ASSERT(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_YEARLY) == 1);
+ CPPUNIT_ASSERT(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_EVERYOTHERYEAR) == 0);
+}
+
+void MyMoneyScheduleTest::testOccurenceToString()
+{
+ // For each occurenceE test MyMoneySchedule::occurenceToString(occurenceE)
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_ONCE) == "Once" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_DAILY) == "Daily" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_WEEKLY) == "Weekly" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYOTHERWEEK) == "Every other week" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_FORTNIGHTLY) == "Fortnightly" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYHALFMONTH) == "Every half month" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYTHREEWEEKS) == "Every three weeks" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYFOURWEEKS) == "Every four weeks" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS) == "Every thirty days" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_MONTHLY) == "Monthly" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS) == "Every eight weeks" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYOTHERMONTH) == "Every two months" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYTHREEMONTHS) == "Every three months" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_QUARTERLY) == "Quarterly" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYFOURMONTHS) == "Every four months" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_TWICEYEARLY) == "Twice yearly" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_YEARLY) == "Yearly" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYOTHERYEAR) == "Every other year" );
+ // For each occurenceE set occurence and compare occurenceToString() with oTS(occurence())
+ MyMoneySchedule s;
+ s.setStartDate(QDate(2007, 1, 1));
+ s.setNextDueDate(s.startDate());
+ s.setLastPayment(s.startDate());
+ s.setOccurence(MyMoneySchedule::OCCUR_ONCE); CPPUNIT_ASSERT(s.occurenceToString() == "Once" );
+ s.setOccurence(MyMoneySchedule::OCCUR_DAILY); CPPUNIT_ASSERT(s.occurenceToString() == "Daily" );
+ s.setOccurence(MyMoneySchedule::OCCUR_WEEKLY); CPPUNIT_ASSERT(s.occurenceToString() == "Weekly" );
+ s.setOccurence(MyMoneySchedule::OCCUR_EVERYOTHERWEEK); CPPUNIT_ASSERT(s.occurenceToString() == "Every other week" );
+ // Fortnightly no longer used: Every other week used instead
+ s.setOccurence(MyMoneySchedule::OCCUR_FORTNIGHTLY); CPPUNIT_ASSERT(s.occurenceToString() == "Every other week" );
+ s.setOccurence(MyMoneySchedule::OCCUR_EVERYHALFMONTH); CPPUNIT_ASSERT(s.occurenceToString() == "Every half month" );
+ s.setOccurence(MyMoneySchedule::OCCUR_EVERYTHREEWEEKS); CPPUNIT_ASSERT(s.occurenceToString() == "Every three weeks" );
+ s.setOccurence(MyMoneySchedule::OCCUR_EVERYFOURWEEKS); CPPUNIT_ASSERT(s.occurenceToString() == "Every four weeks" );
+ s.setOccurence(MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS); CPPUNIT_ASSERT(s.occurenceToString() == "Every thirty days" );
+ s.setOccurence(MyMoneySchedule::OCCUR_MONTHLY); CPPUNIT_ASSERT(s.occurenceToString() == "Monthly" );
+ s.setOccurence(MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS); CPPUNIT_ASSERT(s.occurenceToString() == "Every eight weeks" );
+ s.setOccurence(MyMoneySchedule::OCCUR_EVERYOTHERMONTH); CPPUNIT_ASSERT(s.occurenceToString() == "Every two months" );
+ s.setOccurence(MyMoneySchedule::OCCUR_EVERYTHREEMONTHS); CPPUNIT_ASSERT(s.occurenceToString() == "Every three months" );
+ // Quarterly no longer used. Every three months used instead
+ s.setOccurence(MyMoneySchedule::OCCUR_QUARTERLY); CPPUNIT_ASSERT(s.occurenceToString() == "Every three months" );
+ s.setOccurence(MyMoneySchedule::OCCUR_EVERYFOURMONTHS); CPPUNIT_ASSERT(s.occurenceToString() == "Every four months" );
+ s.setOccurence(MyMoneySchedule::OCCUR_TWICEYEARLY); CPPUNIT_ASSERT(s.occurenceToString() == "Twice yearly" );
+ s.setOccurence(MyMoneySchedule::OCCUR_YEARLY); CPPUNIT_ASSERT(s.occurenceToString() == "Yearly" );
+ s.setOccurence(MyMoneySchedule::OCCUR_EVERYOTHERYEAR); CPPUNIT_ASSERT(s.occurenceToString() == "Every other year" );
+ // Test occurenceToString(mult,occ)
+ // Test all pairs equivalent to simple occurences: should return the same as occurenceToString(simpleOcc)
+ // TODO replace string with (mult,occ) call.
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_ONCE) == MyMoneySchedule::occurenceToString(1,MyMoneySchedule::OCCUR_ONCE) );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_DAILY) == MyMoneySchedule::occurenceToString(1,MyMoneySchedule::OCCUR_DAILY) );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_WEEKLY) == MyMoneySchedule::occurenceToString(1,MyMoneySchedule::OCCUR_WEEKLY) );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYOTHERWEEK) == MyMoneySchedule::occurenceToString(2,MyMoneySchedule::OCCUR_WEEKLY) );
+ // OCCUR_FORTNIGHTLY will no longer be used: only Every Other Week
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYHALFMONTH) == MyMoneySchedule::occurenceToString(1,MyMoneySchedule::OCCUR_EVERYHALFMONTH) );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYTHREEWEEKS) == MyMoneySchedule::occurenceToString(3,MyMoneySchedule::OCCUR_WEEKLY) );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYFOURWEEKS) == MyMoneySchedule::occurenceToString(4,MyMoneySchedule::OCCUR_WEEKLY) );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_MONTHLY) == MyMoneySchedule::occurenceToString(1,MyMoneySchedule::OCCUR_MONTHLY) );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS) == MyMoneySchedule::occurenceToString(8,MyMoneySchedule::OCCUR_WEEKLY) );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYOTHERMONTH) == MyMoneySchedule::occurenceToString(2,MyMoneySchedule::OCCUR_MONTHLY) );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYTHREEMONTHS) == MyMoneySchedule::occurenceToString(3,MyMoneySchedule::OCCUR_MONTHLY) );
+ // OCCUR_QUARTERLY will no longer be used: only Every Three Months
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYFOURMONTHS) == MyMoneySchedule::occurenceToString(4,MyMoneySchedule::OCCUR_MONTHLY) );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_TWICEYEARLY) == MyMoneySchedule::occurenceToString(6,MyMoneySchedule::OCCUR_MONTHLY) );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_YEARLY) == MyMoneySchedule::occurenceToString(1,MyMoneySchedule::OCCUR_YEARLY) );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(MyMoneySchedule::OCCUR_EVERYOTHERYEAR) == MyMoneySchedule::occurenceToString(2,MyMoneySchedule::OCCUR_YEARLY) );
+ // Test additional calls with other mult,occ
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(2,MyMoneySchedule::OCCUR_ONCE) == "2 times" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(2,MyMoneySchedule::OCCUR_DAILY) == "Every 2 days" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(5,MyMoneySchedule::OCCUR_WEEKLY) == "Every 5 weeks" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(2,MyMoneySchedule::OCCUR_EVERYHALFMONTH) == "Every 2 half months" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(5,MyMoneySchedule::OCCUR_MONTHLY) == "Every 5 months" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(3,MyMoneySchedule::OCCUR_YEARLY) == "Every 3 years" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(37,MyMoneySchedule::OCCUR_ONCE) == "37 times" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(43,MyMoneySchedule::OCCUR_DAILY) == "Every 43 days" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(61,MyMoneySchedule::OCCUR_WEEKLY) == "Every 61 weeks" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(73,MyMoneySchedule::OCCUR_EVERYHALFMONTH) == "Every 73 half months" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(83,MyMoneySchedule::OCCUR_MONTHLY) == "Every 83 months" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurenceToString(89,MyMoneySchedule::OCCUR_YEARLY) == "Every 89 years" );
+ // Test instance-level occurenceToString method is using occurencePeriod and multiplier
+ // For each base occurence set occurencePeriod and multiplier
+ s.setOccurencePeriod(MyMoneySchedule::OCCUR_ONCE); s.setOccurenceMultiplier(1);
+ s.setOccurence(MyMoneySchedule::OCCUR_ONCE);
+ s.setOccurenceMultiplier(1); CPPUNIT_ASSERT(s.occurenceToString() == "Once" );
+ s.setOccurenceMultiplier(2); CPPUNIT_ASSERT(s.occurenceToString() == "2 times" );
+ s.setOccurenceMultiplier(3); CPPUNIT_ASSERT(s.occurenceToString() == "3 times" );
+ s.setOccurencePeriod(MyMoneySchedule::OCCUR_DAILY);
+ s.setOccurenceMultiplier(1); CPPUNIT_ASSERT(s.occurenceToString() == "Daily" );
+ s.setOccurenceMultiplier(30); CPPUNIT_ASSERT(s.occurenceToString() == "Every thirty days" );
+ s.setOccurenceMultiplier(3); CPPUNIT_ASSERT(s.occurenceToString() == "Every 3 days" );
+ s.setOccurence(MyMoneySchedule::OCCUR_WEEKLY);
+ CPPUNIT_ASSERT(s.occurenceToString() == "Weekly" );
+ s.setOccurenceMultiplier(2); CPPUNIT_ASSERT(s.occurenceToString() == "Every other week" );
+ s.setOccurenceMultiplier(3); CPPUNIT_ASSERT(s.occurenceToString() == "Every three weeks" );
+ s.setOccurenceMultiplier(4); CPPUNIT_ASSERT(s.occurenceToString() == "Every four weeks" );
+ s.setOccurenceMultiplier(5); CPPUNIT_ASSERT(s.occurenceToString() == "Every 5 weeks" );
+ s.setOccurenceMultiplier(7); CPPUNIT_ASSERT(s.occurenceToString() == "Every 7 weeks" );
+ s.setOccurenceMultiplier(8); CPPUNIT_ASSERT(s.occurenceToString() == "Every eight weeks" );
+ s.setOccurenceMultiplier(9); CPPUNIT_ASSERT(s.occurenceToString() == "Every 9 weeks" );
+ s.setOccurence(MyMoneySchedule::OCCUR_EVERYHALFMONTH);
+ s.setOccurenceMultiplier(1); CPPUNIT_ASSERT(s.occurenceToString() == "Every half month" );
+ s.setOccurenceMultiplier(2); CPPUNIT_ASSERT(s.occurenceToString() == "Every 2 half months" );
+ s.setOccurence(MyMoneySchedule::OCCUR_MONTHLY);
+ s.setOccurenceMultiplier(1); CPPUNIT_ASSERT(s.occurenceToString() == "Monthly" );
+ s.setOccurenceMultiplier(2); CPPUNIT_ASSERT(s.occurenceToString() == "Every two months" );
+ s.setOccurenceMultiplier(3); CPPUNIT_ASSERT(s.occurenceToString() == "Every three months" );
+ s.setOccurenceMultiplier(4); CPPUNIT_ASSERT(s.occurenceToString() == "Every four months" );
+ s.setOccurenceMultiplier(5); CPPUNIT_ASSERT(s.occurenceToString() == "Every 5 months" );
+ s.setOccurenceMultiplier(6); CPPUNIT_ASSERT(s.occurenceToString() == "Twice yearly" );
+ s.setOccurenceMultiplier(7); CPPUNIT_ASSERT(s.occurenceToString() == "Every 7 months" );
+ s.setOccurence(MyMoneySchedule::OCCUR_YEARLY);
+ s.setOccurenceMultiplier(1); CPPUNIT_ASSERT(s.occurenceToString() == "Yearly" );
+ s.setOccurenceMultiplier(2); CPPUNIT_ASSERT(s.occurenceToString() == "Every other year" );
+ s.setOccurenceMultiplier(3); CPPUNIT_ASSERT(s.occurenceToString() == "Every 3 years" );
+}
+
+void MyMoneyScheduleTest::testOccurencePeriodToString()
+{
+ // For each occurenceE test MyMoneySchedule::occurencePeriodToString(occurenceE)
+ // Base occurences are translated
+ CPPUNIT_ASSERT(MyMoneySchedule::occurencePeriodToString(MyMoneySchedule::OCCUR_ONCE) == "Once" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurencePeriodToString(MyMoneySchedule::OCCUR_DAILY) == "Day" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurencePeriodToString(MyMoneySchedule::OCCUR_WEEKLY) == "Week" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurencePeriodToString(MyMoneySchedule::OCCUR_EVERYHALFMONTH) == "Half-month" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurencePeriodToString(MyMoneySchedule::OCCUR_MONTHLY) == "Month" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurencePeriodToString(MyMoneySchedule::OCCUR_YEARLY) == "Year" );
+ // All others are not translated so return Any
+ CPPUNIT_ASSERT(MyMoneySchedule::occurencePeriodToString(MyMoneySchedule::OCCUR_EVERYOTHERWEEK) == "Any" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurencePeriodToString(MyMoneySchedule::OCCUR_FORTNIGHTLY) == "Any" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurencePeriodToString(MyMoneySchedule::OCCUR_EVERYTHREEWEEKS) == "Any" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurencePeriodToString(MyMoneySchedule::OCCUR_EVERYFOURWEEKS) == "Any" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurencePeriodToString(MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS) == "Any" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurencePeriodToString(MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS) == "Any" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurencePeriodToString(MyMoneySchedule::OCCUR_EVERYOTHERMONTH) == "Any" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurencePeriodToString(MyMoneySchedule::OCCUR_EVERYTHREEMONTHS) == "Any" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurencePeriodToString(MyMoneySchedule::OCCUR_QUARTERLY) == "Any" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurencePeriodToString(MyMoneySchedule::OCCUR_EVERYFOURMONTHS) == "Any" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurencePeriodToString(MyMoneySchedule::OCCUR_TWICEYEARLY) == "Any" );
+ CPPUNIT_ASSERT(MyMoneySchedule::occurencePeriodToString(MyMoneySchedule::OCCUR_EVERYOTHERYEAR) == "Any" );
+}
+
+void MyMoneyScheduleTest::testOccurencePeriod()
+{
+ // Each occurence:
+ // Set occurence using setOccurencePeriod
+ // occurencePeriod should match what we set
+ // occurence depends on multiplier
+ // TODO:
+ // Once occurence() and setOccurence() are converting between compound and simple occurences
+ // we need to change the occurence() check and add an occurenceMultiplier() check
+ MyMoneySchedule s;
+ s.setStartDate(QDate(2007, 1, 1));
+ s.setNextDueDate(s.startDate());
+ s.setLastPayment(s.startDate());
+ // Set all base occurences
+ s.setOccurencePeriod(MyMoneySchedule::OCCUR_ONCE);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_ONCE );
+ s.setOccurenceMultiplier(1);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 1);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_ONCE );
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_ONCE );
+ s.setOccurenceMultiplier(2);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 2);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_ONCE );
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_ONCE );
+
+ s.setOccurencePeriod(MyMoneySchedule::OCCUR_DAILY);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_DAILY);
+ s.setOccurenceMultiplier(1);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 1);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_DAILY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_DAILY);
+ s.setOccurenceMultiplier(30);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 30);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_DAILY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS);
+ s.setOccurenceMultiplier(2);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 2);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_DAILY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_DAILY);
+
+ s.setOccurencePeriod(MyMoneySchedule::OCCUR_WEEKLY);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_WEEKLY);
+ s.setOccurenceMultiplier(1);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 1);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_WEEKLY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_WEEKLY);
+ s.setOccurenceMultiplier(2);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 2);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_WEEKLY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYOTHERWEEK);
+ s.setOccurenceMultiplier(3);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 3);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_WEEKLY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYTHREEWEEKS);
+ s.setOccurenceMultiplier(4);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 4);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_WEEKLY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYFOURWEEKS);
+ s.setOccurenceMultiplier(5);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 5);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_WEEKLY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_WEEKLY);
+ s.setOccurenceMultiplier(8);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 8);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_WEEKLY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS);
+
+ s.setOccurencePeriod(MyMoneySchedule::OCCUR_EVERYHALFMONTH);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_EVERYHALFMONTH);
+ s.setOccurenceMultiplier(1);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 1);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_EVERYHALFMONTH);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYHALFMONTH);
+ s.setOccurenceMultiplier(2);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 2);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_EVERYHALFMONTH);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYHALFMONTH);
+
+ s.setOccurencePeriod(MyMoneySchedule::OCCUR_MONTHLY);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_MONTHLY);
+ s.setOccurenceMultiplier(1);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 1);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_MONTHLY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_MONTHLY);
+ s.setOccurenceMultiplier(2);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 2);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_MONTHLY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYOTHERMONTH);
+ s.setOccurenceMultiplier(3);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 3);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_MONTHLY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYTHREEMONTHS);
+ s.setOccurenceMultiplier(4);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 4);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_MONTHLY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYFOURMONTHS);
+ s.setOccurenceMultiplier(5);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 5);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_MONTHLY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_MONTHLY);
+ s.setOccurenceMultiplier(6);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 6);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_MONTHLY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_TWICEYEARLY);
+
+ s.setOccurencePeriod(MyMoneySchedule::OCCUR_YEARLY);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_YEARLY);
+ s.setOccurenceMultiplier(1);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 1);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_YEARLY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_YEARLY);
+ s.setOccurenceMultiplier(2);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 2);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_YEARLY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYOTHERYEAR);
+ s.setOccurenceMultiplier(3);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 3);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_YEARLY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_YEARLY);
+
+ // Set occurence: check occurence, Period and Multiplier
+ s.setOccurence(MyMoneySchedule::OCCUR_ONCE);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_ONCE);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_ONCE);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 1);
+
+ s.setOccurence(MyMoneySchedule::OCCUR_DAILY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_DAILY);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_DAILY);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 1);
+ s.setOccurence(MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_DAILY);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 30);
+
+ s.setOccurence(MyMoneySchedule::OCCUR_WEEKLY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_WEEKLY);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_WEEKLY);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 1);
+ s.setOccurence(MyMoneySchedule::OCCUR_EVERYOTHERWEEK);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYOTHERWEEK);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_WEEKLY);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 2);
+ // Fortnightly no longer used: Every other week used instead
+ s.setOccurence(MyMoneySchedule::OCCUR_FORTNIGHTLY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYOTHERWEEK);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_WEEKLY);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 2);
+ s.setOccurence(MyMoneySchedule::OCCUR_EVERYTHREEWEEKS);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYTHREEWEEKS);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_WEEKLY);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 3);
+ s.setOccurence(MyMoneySchedule::OCCUR_EVERYFOURWEEKS);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYFOURWEEKS);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_WEEKLY);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 4);
+ s.setOccurence(MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_WEEKLY);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 8);
+
+ s.setOccurence(MyMoneySchedule::OCCUR_EVERYHALFMONTH);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYHALFMONTH);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_EVERYHALFMONTH);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 1);
+
+ s.setOccurence(MyMoneySchedule::OCCUR_MONTHLY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_MONTHLY);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_MONTHLY);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 1);
+ s.setOccurence(MyMoneySchedule::OCCUR_EVERYOTHERMONTH);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYOTHERMONTH);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_MONTHLY);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 2);
+ s.setOccurence(MyMoneySchedule::OCCUR_EVERYTHREEMONTHS);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYTHREEMONTHS);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_MONTHLY);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 3);
+ // Quarterly no longer used. Every three months used instead
+ s.setOccurence(MyMoneySchedule::OCCUR_QUARTERLY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYTHREEMONTHS);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_MONTHLY);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 3);
+ s.setOccurence(MyMoneySchedule::OCCUR_EVERYFOURMONTHS);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYFOURMONTHS);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_MONTHLY);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 4);
+ s.setOccurence(MyMoneySchedule::OCCUR_TWICEYEARLY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_TWICEYEARLY);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_MONTHLY);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 6);
+
+ s.setOccurence(MyMoneySchedule::OCCUR_YEARLY);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_YEARLY);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_YEARLY);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 1);
+ s.setOccurence(MyMoneySchedule::OCCUR_EVERYOTHERYEAR);
+ CPPUNIT_ASSERT(s.occurence() == MyMoneySchedule::OCCUR_EVERYOTHERYEAR);
+ CPPUNIT_ASSERT(s.occurencePeriod() == MyMoneySchedule::OCCUR_YEARLY);
+ CPPUNIT_ASSERT(s.occurenceMultiplier() == 2);
+}
+
+void MyMoneyScheduleTest::testSimpleToFromCompoundOccurence()
+{
+ // Conversion between Simple and Compound occurences
+ // Each simple occurence to compound occurence
+ MyMoneySchedule::occurenceE occ;
+ int mult;
+ occ = MyMoneySchedule::OCCUR_ONCE; mult = 1;
+ MyMoneySchedule::simpleToCompoundOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_ONCE && mult == 1 );
+ occ = MyMoneySchedule::OCCUR_DAILY; mult = 1;
+ MyMoneySchedule::simpleToCompoundOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_DAILY && mult == 1 );
+ occ = MyMoneySchedule::OCCUR_WEEKLY; mult = 1;
+ MyMoneySchedule::simpleToCompoundOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_WEEKLY && mult == 1 );
+ occ = MyMoneySchedule::OCCUR_EVERYOTHERWEEK; mult = 1;
+ MyMoneySchedule::simpleToCompoundOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_WEEKLY && mult == 2 );
+ occ = MyMoneySchedule::OCCUR_FORTNIGHTLY; mult = 1;
+ MyMoneySchedule::simpleToCompoundOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_WEEKLY && mult == 2 );
+ occ = MyMoneySchedule::OCCUR_EVERYHALFMONTH; mult = 1;
+ MyMoneySchedule::simpleToCompoundOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_EVERYHALFMONTH && mult == 1 );
+ occ = MyMoneySchedule::OCCUR_EVERYTHREEWEEKS; mult = 1;
+ MyMoneySchedule::simpleToCompoundOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_WEEKLY && mult == 3 );
+ occ = MyMoneySchedule::OCCUR_EVERYFOURWEEKS; mult = 1;
+ MyMoneySchedule::simpleToCompoundOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_WEEKLY && mult == 4 );
+ occ = MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS; mult = 1;
+ MyMoneySchedule::simpleToCompoundOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_DAILY && mult == 30 );
+ occ = MyMoneySchedule::OCCUR_MONTHLY; mult = 1;
+ MyMoneySchedule::simpleToCompoundOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_MONTHLY && mult == 1 );
+ occ = MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS; mult = 1;
+ MyMoneySchedule::simpleToCompoundOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_WEEKLY && mult == 8 );
+ occ = MyMoneySchedule::OCCUR_EVERYOTHERMONTH; mult = 1;
+ MyMoneySchedule::simpleToCompoundOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_MONTHLY && mult == 2 );
+ occ = MyMoneySchedule::OCCUR_EVERYTHREEMONTHS; mult = 1;
+ MyMoneySchedule::simpleToCompoundOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_MONTHLY && mult == 3 );
+ occ = MyMoneySchedule::OCCUR_QUARTERLY; mult = 1;
+ MyMoneySchedule::simpleToCompoundOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_MONTHLY && mult == 3 );
+ occ = MyMoneySchedule::OCCUR_EVERYFOURMONTHS; mult = 1;
+ MyMoneySchedule::simpleToCompoundOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_MONTHLY && mult == 4 );
+ occ = MyMoneySchedule::OCCUR_TWICEYEARLY; mult = 1;
+ MyMoneySchedule::simpleToCompoundOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_MONTHLY && mult == 6 );
+ occ = MyMoneySchedule::OCCUR_YEARLY; mult = 1;
+ MyMoneySchedule::simpleToCompoundOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_YEARLY && mult == 1 );
+ occ = MyMoneySchedule::OCCUR_EVERYOTHERYEAR; mult = 1;
+ MyMoneySchedule::simpleToCompoundOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_YEARLY && mult == 2 );
+ // Compound to Simple Occurences
+ occ = MyMoneySchedule::OCCUR_ONCE; mult = 1;
+ MyMoneySchedule::compoundToSimpleOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_ONCE && mult == 1 );
+ occ = MyMoneySchedule::OCCUR_DAILY; mult = 1;
+ MyMoneySchedule::compoundToSimpleOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_DAILY && mult == 1 );
+ occ = MyMoneySchedule::OCCUR_WEEKLY; mult = 1;
+ MyMoneySchedule::compoundToSimpleOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_WEEKLY && mult == 1 );
+ occ = MyMoneySchedule::OCCUR_WEEKLY; mult = 2;
+ MyMoneySchedule::compoundToSimpleOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_EVERYOTHERWEEK && mult == 1 );
+ // MyMoneySchedule::OCCUR_FORTNIGHTLY not converted back
+ occ = MyMoneySchedule::OCCUR_EVERYHALFMONTH; mult = 1;
+ MyMoneySchedule::compoundToSimpleOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_EVERYHALFMONTH && mult == 1 );
+ occ = MyMoneySchedule::OCCUR_WEEKLY; mult = 3;
+ MyMoneySchedule::compoundToSimpleOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_EVERYTHREEWEEKS && mult == 1 );
+ occ = MyMoneySchedule::OCCUR_WEEKLY ; mult = 4;
+ MyMoneySchedule::compoundToSimpleOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_EVERYFOURWEEKS && mult == 1 );
+ occ = MyMoneySchedule::OCCUR_DAILY; mult = 30;
+ MyMoneySchedule::compoundToSimpleOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS && mult == 1 );
+ occ = MyMoneySchedule::OCCUR_MONTHLY; mult = 1;
+ MyMoneySchedule::compoundToSimpleOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_MONTHLY && mult == 1 );
+ occ = MyMoneySchedule::OCCUR_WEEKLY; mult = 8;
+ MyMoneySchedule::compoundToSimpleOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS && mult == 1 );
+ occ = MyMoneySchedule::OCCUR_MONTHLY; mult = 2;
+ MyMoneySchedule::compoundToSimpleOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_EVERYOTHERMONTH && mult == 1 );
+ occ = MyMoneySchedule::OCCUR_MONTHLY; mult = 3;
+ MyMoneySchedule::compoundToSimpleOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_EVERYTHREEMONTHS && mult == 1 );
+ // MyMoneySchedule::OCCUR_QUARTERLY not converted back
+ occ = MyMoneySchedule::OCCUR_MONTHLY; mult = 4;
+ MyMoneySchedule::compoundToSimpleOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_EVERYFOURMONTHS && mult == 1 );
+ occ = MyMoneySchedule::OCCUR_MONTHLY; mult = 6;
+ MyMoneySchedule::compoundToSimpleOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_TWICEYEARLY && mult == 1 );
+ occ = MyMoneySchedule::OCCUR_YEARLY; mult = 1;
+ MyMoneySchedule::compoundToSimpleOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_YEARLY && mult == 1 );
+ occ = MyMoneySchedule::OCCUR_YEARLY; mult = 2;
+ MyMoneySchedule::compoundToSimpleOccurence(mult, occ);
+ CPPUNIT_ASSERT( occ == MyMoneySchedule::OCCUR_EVERYOTHERYEAR && mult == 1 );
+}
+
+void MyMoneyScheduleTest::testPaidEarlyOneTime()
+{
+// this tries to figure out what's wrong with
+// https://bugs.kde.org/show_bug.cgi?id=231029
+
+ MyMoneySchedule sch;
+ QDate paymentInFuture = QDate::currentDate().addDays(7);
+
+ QString ref_ok = QString(
+ "<!DOCTYPE TEST>\n"
+ "<SCHEDULE-CONTAINER>\n"
+ " <SCHEDULED_TX startDate=\"%1\" autoEnter=\"0\" weekendOption=\"1\" lastPayment=\"%2\" paymentType=\"2\" endDate=\"%3\" type=\"4\" id=\"SCH0042\" name=\"A Name\" fixed=\"1\" occurenceMultiplier=\"1\" occurence=\"32\" >\n"
+ " <PAYMENTS/>\n"
+ " <TRANSACTION postdate=\"\" memo=\"\" id=\"\" commodity=\"GBP\" entrydate=\"\" >\n"
+ " <SPLITS>\n"
+ " <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"96379/100\" action=\"Transfer\" number=\"\" reconcileflag=\"2\" memo=\"\" value=\"96379/100\" id=\"S0001\" account=\"A000076\" />\n"
+ " <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"-96379/100\" action=\"Transfer\" number=\"\" reconcileflag=\"2\" memo=\"\" value=\"-96379/100\" id=\"S0002\" account=\"A000276\" />\n"
+ " </SPLITS>\n"
+ " </TRANSACTION>\n"
+ " </SCHEDULED_TX>\n"
+ "</SCHEDULE-CONTAINER>\n"
+ ).arg(paymentInFuture.toString(Qt::ISODate))
+ .arg(paymentInFuture.toString(Qt::ISODate))
+ .arg(paymentInFuture.toString(Qt::ISODate));
+
+ QDomDocument doc;
+ QDomElement node;
+ doc.setContent(ref_ok);
+ node = doc.documentElement().firstChild().toElement();
+
+ try {
+ sch = MyMoneySchedule(node);
+ CPPUNIT_ASSERT(sch.isFinished() == true);
+ CPPUNIT_ASSERT(sch.occurencePeriod() == MyMoneySchedule::OCCUR_MONTHLY);
+ CPPUNIT_ASSERT(sch.paymentDates(QDate::currentDate(), QDate::currentDate().addDays(21)).count() == 0);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+}
diff --git a/kmymoney2/mymoney/mymoneyscheduletest.h b/kmymoney2/mymoney/mymoneyscheduletest.h
new file mode 100644
index 0000000..ea2c929
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyscheduletest.h
@@ -0,0 +1,106 @@
+/***************************************************************************
+ mymoneyscheduletest.h
+ -------------------
+ copyright : (C) 2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 __MYMONEYSCHEDULETEST_H__
+#define __MYMONEYSCHEDULETEST_H__
+
+#include <cppunit/extensions/HelperMacros.h>
+
+#define private public
+#define protected public
+#include "mymoneyscheduled.h"
+#include "mymoneyfile.h"
+#include "storage/mymoneyseqaccessmgr.h"
+#undef private
+
+class MyMoneyScheduleTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(MyMoneyScheduleTest);
+ CPPUNIT_TEST(testEmptyConstructor);
+ CPPUNIT_TEST(testConstructor);
+ CPPUNIT_TEST(testSetFunctions);
+ CPPUNIT_TEST(testCopyConstructor);
+ CPPUNIT_TEST(testAssignmentConstructor);
+ // The following tests must be done in this order.
+ CPPUNIT_TEST(testSingleton);
+ CPPUNIT_TEST(testAddSchedule);
+ CPPUNIT_TEST(testAnyScheduled);
+ CPPUNIT_TEST(testOverdue);
+ CPPUNIT_TEST(testGetSchedule);
+ CPPUNIT_TEST(testGetScheduled);
+ CPPUNIT_TEST(testGetOverdue);
+ CPPUNIT_TEST(testNextPayment);
+ CPPUNIT_TEST(testPaymentDates);
+ CPPUNIT_TEST(testReplaceSchedule);
+ CPPUNIT_TEST(testRemoveSchedule);
+ CPPUNIT_TEST(testWriteXML);
+ CPPUNIT_TEST(testReadXML);
+ CPPUNIT_TEST(testHasReferenceTo);
+ CPPUNIT_TEST(testAdjustedNextDueDate);
+ CPPUNIT_TEST(testModifyNextDueDate);
+ CPPUNIT_TEST(testDaysBetweenEvents);
+ CPPUNIT_TEST(testEventsPerYear);
+ CPPUNIT_TEST(testAddHalfMonths);
+ CPPUNIT_TEST(testOccurenceToString);
+ CPPUNIT_TEST(testOccurencePeriodToString);
+ CPPUNIT_TEST(testStringToOccurence);
+ CPPUNIT_TEST(testOccurencePeriod);
+ CPPUNIT_TEST(testSimpleToFromCompoundOccurence);
+ CPPUNIT_TEST(testPaidEarlyOneTime);
+ CPPUNIT_TEST_SUITE_END();
+
+protected:
+// MyMoneyFile *m_file;
+// MyMoneySeqAccessMgr* storage;
+ //TestObserverSet *observer;
+ //TestObserverSet *hierarchyObserver;
+
+public:
+ MyMoneyScheduleTest();
+ void setUp ();
+ void tearDown ();
+ void testEmptyConstructor();
+ void testConstructor();
+ void testSetFunctions();
+ void testCopyConstructor();
+ void testAssignmentConstructor();
+ void testSingleton();
+ void testAddSchedule();
+ void testAnyScheduled();
+ void testOverdue();
+ void testGetSchedule();
+ void testGetScheduled();
+ void testGetOverdue();
+ void testNextPayment();
+ void testAddHalfMonths();
+ void testPaymentDates();
+ void testReplaceSchedule();
+ void testRemoveSchedule();
+ void testWriteXML();
+ void testReadXML();
+ void testHasReferenceTo();
+ void testAdjustedNextDueDate();
+ void testModifyNextDueDate();
+ void testDaysBetweenEvents();
+ void testEventsPerYear();
+ void testOccurenceToString();
+ void testOccurencePeriodToString();
+ void testStringToOccurence();
+ void testOccurencePeriod();
+ void testSimpleToFromCompoundOccurence();
+ void testPaidEarlyOneTime();
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneysecurity.cpp b/kmymoney2/mymoney/mymoneysecurity.cpp
new file mode 100644
index 0000000..73edbdb
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneysecurity.cpp
@@ -0,0 +1,180 @@
+/***************************************************************************
+ mymoneysecurity.cpp - description
+ -------------------
+ begin : Tue Jan 29 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+// ----------------------------------------------------------------------------
+// KDE Includes
+
+#include <klocale.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "mymoneysecurity.h"
+#include "mymoneyexception.h"
+
+MyMoneySecurity::MyMoneySecurity() :
+ m_securityType(SECURITY_NONE),
+ m_smallestAccountFraction(100),
+ m_smallestCashFraction(100),
+ m_partsPerUnit(100)
+{
+}
+
+MyMoneySecurity::MyMoneySecurity(const QString& id, const QString& name, const QString& symbol, const int partsPerUnit, const int smallestCashFraction, const int smallestAccountFraction) :
+ MyMoneyObject(id),
+ m_name(name),
+ m_securityType(SECURITY_CURRENCY)
+{
+ if(symbol.isEmpty())
+ m_tradingSymbol = id;
+ else
+ m_tradingSymbol = symbol;
+
+ m_partsPerUnit = partsPerUnit;
+ m_smallestCashFraction = smallestCashFraction;
+ if(smallestAccountFraction)
+ m_smallestAccountFraction = smallestAccountFraction;
+ else
+ m_smallestAccountFraction = smallestCashFraction;
+}
+
+MyMoneySecurity::MyMoneySecurity(const QString& id, const MyMoneySecurity& equity) :
+ MyMoneyObject(id)
+{
+ *this = equity;
+ m_id = id;
+}
+
+MyMoneySecurity::MyMoneySecurity(const QDomElement& node) :
+ MyMoneyObject(node),
+ MyMoneyKeyValueContainer(node.elementsByTagName("KEYVALUEPAIRS").item(0).toElement())
+{
+ if(("SECURITY" != node.tagName())
+ && ("EQUITY" != node.tagName())
+ && ("CURRENCY" != node.tagName()))
+ throw new MYMONEYEXCEPTION("Node was not SECURITY or CURRENCY");
+
+ setName(QStringEmpty(node.attribute("name")));
+ setTradingSymbol(QStringEmpty(node.attribute("symbol")));
+ setSecurityType(static_cast<eSECURITYTYPE>(node.attribute("type").toInt()));
+ setSmallestAccountFraction(node.attribute("saf").toInt());
+
+ if(isCurrency()) {
+ setPartsPerUnit(node.attribute("ppu").toInt());
+ setSmallestCashFraction(node.attribute("scf").toInt());
+ } else {
+ setTradingCurrency(QStringEmpty(node.attribute("trading-currency")));
+ setTradingMarket(QStringEmpty(node.attribute("trading-market")));
+ }
+}
+
+MyMoneySecurity::~MyMoneySecurity()
+{
+}
+
+bool MyMoneySecurity::operator == (const MyMoneySecurity& r) const
+{
+ return (m_id == r.m_id)
+ && (m_name == r.m_name)
+ && (m_tradingSymbol == r.m_tradingSymbol)
+ && (m_tradingMarket == r.m_tradingMarket)
+ && (m_tradingSymbol == r.m_tradingSymbol)
+ && (m_tradingCurrency == r.m_tradingCurrency)
+ && (m_securityType == r.m_securityType)
+ && (m_smallestAccountFraction == r.m_smallestAccountFraction)
+ && (m_smallestCashFraction == r.m_smallestCashFraction)
+ && (m_partsPerUnit == r.m_partsPerUnit)
+ && this->MyMoneyKeyValueContainer::operator == (r);
+
+}
+
+bool MyMoneySecurity::operator < (const MyMoneySecurity& right) const
+{
+ if(m_securityType == right.m_securityType)
+ return m_name < right.m_name;
+ return m_securityType < right.m_securityType;
+}
+
+
+bool MyMoneySecurity::hasReferenceTo(const QString& id) const
+{
+ return (id == m_tradingCurrency);
+}
+
+void MyMoneySecurity::writeXML(QDomDocument& document, QDomElement& parent) const
+{
+ QDomElement el;
+ if(isCurrency())
+ el = document.createElement("CURRENCY");
+ else
+ el = document.createElement("SECURITY");
+
+ writeBaseXML(document, el);
+
+ el.setAttribute("name", m_name);
+ el.setAttribute("symbol", m_tradingSymbol);
+ el.setAttribute("type", static_cast<int>(m_securityType));
+ el.setAttribute("saf", m_smallestAccountFraction);
+ if(isCurrency()) {
+ el.setAttribute("ppu", m_partsPerUnit);
+ el.setAttribute("scf", m_smallestCashFraction);
+ } else {
+ el.setAttribute("trading-currency", m_tradingCurrency);
+ el.setAttribute("trading-market", m_tradingMarket);
+ }
+
+ //Add in Key-Value Pairs for securities.
+ MyMoneyKeyValueContainer::writeXML(document, el);
+
+ parent.appendChild(el);
+}
+
+QString MyMoneySecurity::securityTypeToString(const eSECURITYTYPE securityType)
+{
+ QString returnString;
+
+ switch (securityType) {
+ case MyMoneySecurity::SECURITY_STOCK:
+ returnString = I18N_NOOP("Stock");
+ break;
+ case MyMoneySecurity::SECURITY_MUTUALFUND:
+ returnString = I18N_NOOP("Mutual Fund");
+ break;
+ case MyMoneySecurity::SECURITY_BOND:
+ returnString = I18N_NOOP("Bond");
+ break;
+ case MyMoneySecurity::SECURITY_CURRENCY:
+ returnString = I18N_NOOP("Currency");
+ break;
+ case MyMoneySecurity::SECURITY_NONE:
+ returnString = I18N_NOOP("None");
+ break;
+ default:
+ returnString = I18N_NOOP("Unknown");
+ }
+
+ return returnString;
+}
+
diff --git a/kmymoney2/mymoney/mymoneysecurity.h b/kmymoney2/mymoney/mymoneysecurity.h
new file mode 100644
index 0000000..666398e
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneysecurity.h
@@ -0,0 +1,150 @@
+/***************************************************************************
+ mymoneysecurity.h - description
+ -------------------
+ begin : Tue Jan 29 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYSECURITY_H
+#define MYMONEYSECURITY_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qdatetime.h>
+#include <qmap.h>
+
+// ----------------------------------------------------------------------------
+// KDE Includes
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include <kmymoney/export.h>
+#include <kmymoney/mymoneymoney.h>
+#include <kmymoney/mymoneyutils.h>
+#include <kmymoney/mymoneyobject.h>
+#include <kmymoney/mymoneykeyvaluecontainer.h>
+
+/**
+ * Class that holds all the required information about a security that the user
+ * has entered information about. A security can be a stock, a mutual fund, a bond
+ * or a currency.
+ *
+ * @author Kevin Tambascio
+ * @author Thomas Baumgart
+ */
+class KMYMONEY_EXPORT MyMoneySecurity : public MyMoneyObject, public MyMoneyKeyValueContainer
+{
+public:
+ MyMoneySecurity();
+ MyMoneySecurity(const QString& id, const MyMoneySecurity& equity);
+ MyMoneySecurity(const QString& id, const QString& name, const QString& symbol = QString(), const int partsPerUnit = 100, const int smallestCashFraction = 100, const int smallestAccountFraction = 0);
+ MyMoneySecurity(const QDomElement& node);
+ virtual ~MyMoneySecurity();
+
+ bool operator < (const MyMoneySecurity&) const;
+
+ /**
+ * This operator tests for equality of two MyMoneySecurity objects
+ */
+ bool operator == (const MyMoneySecurity&) const;
+
+ /**
+ * This operator tests for inequality of this MyMoneySecurity object
+ * and the one passed by @p r
+ *
+ * @param r the right side of the comparison
+ */
+ bool operator != (const MyMoneySecurity& r) const { return !(*this == r); }
+
+public:
+ typedef enum {
+ SECURITY_STOCK,
+ SECURITY_MUTUALFUND,
+ SECURITY_BOND,
+ SECURITY_CURRENCY,
+ SECURITY_NONE
+ } eSECURITYTYPE;
+
+ const QString& name() const { return m_name; }
+ void setName(const String& str) { m_name = str; }
+
+ const QString& tradingSymbol() const { return m_tradingSymbol; }
+ void setTradingSymbol(const String& str) { m_tradingSymbol = str; }
+
+ eSECURITYTYPE securityType() const { return m_securityType; }
+ void setSecurityType(const eSECURITYTYPE& s) { m_securityType = s; }
+ bool isCurrency(void) const { return m_securityType == SECURITY_CURRENCY; };
+
+ const QString& tradingMarket() const { return m_tradingMarket; }
+ void setTradingMarket(const QString& str) { m_tradingMarket = str; }
+
+ const QString& tradingCurrency(void) const { return m_tradingCurrency; };
+ void setTradingCurrency(const QString& str) { m_tradingCurrency = str; };
+
+ int smallestAccountFraction(void) const { return m_smallestAccountFraction; };
+ void setSmallestAccountFraction(const int sf) { m_smallestAccountFraction = sf; };
+
+ int partsPerUnit(void) const { return m_partsPerUnit; };
+ int smallestCashFraction(void) const { return m_smallestCashFraction; };
+
+ void setPartsPerUnit(const int ppu) { m_partsPerUnit = ppu; };
+ void setSmallestCashFraction(const int sf) { m_smallestCashFraction = sf; };
+
+ void writeXML(QDomDocument& document, QDomElement& parent) const;
+
+ /**
+ * This method checks if a reference to the given object exists. It returns,
+ * a @p true if the object is referencing the one requested by the
+ * parameter @p id. If it does not, this method returns @p false.
+ *
+ * @param id id of the object to be checked for references
+ * @retval true This object references object with id @p id.
+ * @retval false This object does not reference the object with id @p id.
+ */
+ bool hasReferenceTo(const QString& id) const;
+
+ /**
+ * This method is used to convert the internal representation of
+ * an security type into a human readable format
+ *
+ * @param securityType enumerated representation of the security type.
+ * For possible values, see MyMoneySecurity::eSECURITYTYPE
+ *
+ * @return QString representing the human readable form
+ */
+ static QString securityTypeToString(const MyMoneySecurity::eSECURITYTYPE securityType);
+
+
+protected:
+ QString m_name;
+ QString m_tradingSymbol;
+ QString m_tradingMarket;
+ QString m_tradingCurrency;
+ eSECURITYTYPE m_securityType;
+ int m_smallestAccountFraction;
+ int m_smallestCashFraction;
+ int m_partsPerUnit;
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneysecuritytest.cpp b/kmymoney2/mymoney/mymoneysecuritytest.cpp
new file mode 100644
index 0000000..77b6764
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneysecuritytest.cpp
@@ -0,0 +1,210 @@
+/***************************************************************************
+ mymoneysecuritytest.cpp
+ -------------------
+ copyright : (C) 2002 by Kevin Tambascio
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "mymoneysecuritytest.h"
+
+MyMoneySecurityTest::MyMoneySecurityTest()
+{
+}
+
+
+void MyMoneySecurityTest::setUp () {
+ m = new MyMoneySecurity();
+}
+
+void MyMoneySecurityTest::tearDown () {
+ delete m;
+}
+
+void MyMoneySecurityTest::testEmptyConstructor() {
+ CPPUNIT_ASSERT(m->id().isEmpty());
+ CPPUNIT_ASSERT(m->name().isEmpty());
+ CPPUNIT_ASSERT(m->tradingSymbol().isEmpty());
+ CPPUNIT_ASSERT(m->securityType() == MyMoneySecurity::SECURITY_NONE);
+ CPPUNIT_ASSERT(m->tradingMarket().isEmpty());
+ CPPUNIT_ASSERT(m->tradingCurrency().isEmpty());
+ CPPUNIT_ASSERT(m->smallestCashFraction() == 100);
+ CPPUNIT_ASSERT(m->smallestAccountFraction() == 100);
+ CPPUNIT_ASSERT(m->partsPerUnit() == 100);
+}
+
+void MyMoneySecurityTest::testCopyConstructor() {
+ MyMoneySecurity* n1 = new MyMoneySecurity("GUID1", *m);
+ MyMoneySecurity n2(*n1);
+
+ // CPPUNIT_ASSERT(*n1 == n2);
+
+ delete n1;
+}
+
+void MyMoneySecurityTest::testNonemptyConstructor() {
+ QDate date(2004,4,1);
+ MyMoneyMoney val("1234/100");
+
+ m->setName("name");
+ m->setTradingSymbol("symbol");
+ m->setSecurityType(MyMoneySecurity::SECURITY_CURRENCY);
+ // m->addPriceHistory(date, val);
+
+ MyMoneySecurity n("id", *m);
+
+ CPPUNIT_ASSERT(n.id() == QString("id"));
+ CPPUNIT_ASSERT(n.tradingSymbol() == QString("symbol"));
+ CPPUNIT_ASSERT(n.securityType() == MyMoneySecurity::SECURITY_CURRENCY);
+ // CPPUNIT_ASSERT(n.priceHistory().count() == 1);
+}
+
+
+void MyMoneySecurityTest::testSetFunctions() {
+ m->setName("Name");
+ m->setTradingSymbol("Symbol");
+ m->setTradingMarket("Market");
+ m->setTradingCurrency("Currency");
+ m->setSecurityType(MyMoneySecurity::SECURITY_STOCK);
+ m->setSmallestAccountFraction(50);
+ m->setSmallestCashFraction(2);
+ m->setPartsPerUnit(30);
+
+ CPPUNIT_ASSERT(m->name() == "Name");
+ CPPUNIT_ASSERT(m->tradingSymbol() == "Symbol");
+ CPPUNIT_ASSERT(m->tradingMarket() == "Market");
+ CPPUNIT_ASSERT(m->tradingCurrency() == "Currency");
+ CPPUNIT_ASSERT(m->securityType() == MyMoneySecurity::SECURITY_STOCK);
+ CPPUNIT_ASSERT(m->smallestAccountFraction() == 50);
+ CPPUNIT_ASSERT(m->smallestCashFraction() == 2);
+ CPPUNIT_ASSERT(m->partsPerUnit() == 30);
+}
+
+/*
+void MyMoneySecurityTest::testMyMoneyFileConstructor() {
+ MyMoneySecurity *t = new MyMoneySecurity("GUID", *n);
+
+ CPPUNIT_ASSERT(t->id() == "GUID");
+
+ delete t;
+}
+*/
+
+void MyMoneySecurityTest::testEquality () {
+ testSetFunctions();
+ m->setValue("Key", "Value");
+
+ MyMoneySecurity n;
+ n = *m;
+
+ CPPUNIT_ASSERT(n == *m);
+ n.setName("NewName");
+ CPPUNIT_ASSERT(!(n == *m));
+ n = *m;
+ n.setTradingSymbol("NewSymbol");
+ CPPUNIT_ASSERT(!(n == *m));
+ n = *m;
+ n.setTradingMarket("NewMarket");
+ CPPUNIT_ASSERT(!(n == *m));
+ n = *m;
+ n.setTradingCurrency("NewCurrency");
+ CPPUNIT_ASSERT(!(n == *m));
+ n = *m;
+ n.setSecurityType(MyMoneySecurity::SECURITY_CURRENCY);
+ CPPUNIT_ASSERT(!(n == *m));
+ n = *m;
+ n.setSmallestAccountFraction(40);
+ CPPUNIT_ASSERT(!(n == *m));
+ n = *m;
+ n.setSmallestCashFraction(20);
+ CPPUNIT_ASSERT(!(n == *m));
+ n = *m;
+ n.setPartsPerUnit(3);
+ CPPUNIT_ASSERT(!(n == *m));
+ n = *m;
+ n.setValue("Key", "NewValue");
+ CPPUNIT_ASSERT(!(n == *m));
+}
+
+void MyMoneySecurityTest::testInequality () {
+ testSetFunctions();
+ m->setValue("Key", "Value");
+
+ MyMoneySecurity n;
+ n = *m;
+
+ CPPUNIT_ASSERT(!(n != *m));
+ n.setName("NewName");
+ CPPUNIT_ASSERT(n != *m);
+ n = *m;
+ n.setTradingSymbol("NewSymbol");
+ CPPUNIT_ASSERT(n != *m);
+ n = *m;
+ n.setTradingMarket("NewMarket");
+ CPPUNIT_ASSERT(n != *m);
+ n = *m;
+ n.setTradingCurrency("NewCurrency");
+ CPPUNIT_ASSERT(n != *m);
+ n = *m;
+ n.setSecurityType(MyMoneySecurity::SECURITY_CURRENCY);
+ CPPUNIT_ASSERT(n != *m);
+ n = *m;
+ n.setSmallestAccountFraction(40);
+ CPPUNIT_ASSERT(n != *m);
+ n = *m;
+ n.setSmallestCashFraction(20);
+ CPPUNIT_ASSERT(n != *m);
+ n = *m;
+ n.setPartsPerUnit(3);
+ CPPUNIT_ASSERT(n != *m);
+ n = *m;
+ n.setValue("Key", "NewValue");
+ CPPUNIT_ASSERT(n != *m);
+}
+
+/*
+void MyMoneySecurityTest::testAccountIDList () {
+ MyMoneySecurity equity;
+ QStringList list;
+ QString id;
+
+ // list must be empty
+ list = institution.accountList();
+ CPPUNIT_ASSERT(list.count() == 0);
+
+ // add one account
+ institution.addAccountId("A000002");
+ list = institution.accountList();
+ CPPUNIT_ASSERT(list.count() == 1);
+ CPPUNIT_ASSERT(list.contains("A000002") == 1);
+
+ // adding same account shouldn't make a difference
+ institution.addAccountId("A000002");
+ list = institution.accountList();
+ CPPUNIT_ASSERT(list.count() == 1);
+ CPPUNIT_ASSERT(list.contains("A000002") == 1);
+
+ // now add another account
+ institution.addAccountId("A000001");
+ list = institution.accountList();
+ CPPUNIT_ASSERT(list.count() == 2);
+ CPPUNIT_ASSERT(list.contains("A000002") == 1);
+ CPPUNIT_ASSERT(list.contains("A000001") == 1);
+
+ id = institution.removeAccountId("A000001");
+ CPPUNIT_ASSERT(id == "A000001");
+ list = institution.accountList();
+ CPPUNIT_ASSERT(list.count() == 1);
+ CPPUNIT_ASSERT(list.contains("A000002") == 1);
+
+}
+*/
+
diff --git a/kmymoney2/mymoney/mymoneysecuritytest.h b/kmymoney2/mymoney/mymoneysecuritytest.h
new file mode 100644
index 0000000..39d0cd4
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneysecuritytest.h
@@ -0,0 +1,58 @@
+/***************************************************************************
+ mymoneysecuritytest.h
+ -------------------
+ copyright : (C) 2004 by Kevin Tambascio
+ email : ktambascio@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 __MYMONEYSECURITYTEST_H__
+#define __MYMONEYSECURITYTEST_H__
+
+#include <cppunit/extensions/HelperMacros.h>
+
+#define private public
+#include "mymoneysecurity.h"
+#undef private
+
+class MyMoneySecurityTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(MyMoneySecurityTest);
+ CPPUNIT_TEST(testEmptyConstructor);
+ CPPUNIT_TEST(testNonemptyConstructor);
+ CPPUNIT_TEST(testCopyConstructor);
+ CPPUNIT_TEST(testSetFunctions);
+ CPPUNIT_TEST(testEquality);
+ CPPUNIT_TEST(testInequality);
+/*
+ CPPUNIT_TEST(testMyMoneyFileConstructor);
+ CPPUNIT_TEST(testAccountIDList);
+*/
+ CPPUNIT_TEST_SUITE_END();
+
+protected:
+ MyMoneySecurity *m;
+
+public:
+ MyMoneySecurityTest();
+
+ void setUp ();
+ void tearDown ();
+ void testEmptyConstructor();
+ void testNonemptyConstructor();
+ void testCopyConstructor();
+ void testSetFunctions();
+ void testEquality ();
+ void testInequality ();
+ // void testMyMoneyFileConstructor();
+ // void testAccountIDList ();
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneysplit.cpp b/kmymoney2/mymoney/mymoneysplit.cpp
new file mode 100644
index 0000000..a19ff43
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneysplit.cpp
@@ -0,0 +1,272 @@
+/***************************************************************************
+ mymoneysplit.cpp - description
+ -------------------
+ begin : Sun Apr 28 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "mymoneysplit.h"
+#include "mymoneytransaction.h"
+
+const char MyMoneySplit::ActionCheck[] = "Check";
+const char MyMoneySplit::ActionDeposit[] = "Deposit";
+const char MyMoneySplit::ActionTransfer[] = "Transfer";
+const char MyMoneySplit::ActionWithdrawal[] = "Withdrawal";
+const char MyMoneySplit::ActionATM[] = "ATM";
+
+const char MyMoneySplit::ActionAmortization[] = "Amortization";
+const char MyMoneySplit::ActionInterest[] = "Interest";
+
+const char MyMoneySplit::ActionBuyShares[] = "Buy";
+const char MyMoneySplit::ActionDividend[] = "Dividend";
+const char MyMoneySplit::ActionReinvestDividend[] = "Reinvest";
+const char MyMoneySplit::ActionYield[] = "Yield";
+const char MyMoneySplit::ActionAddShares[] = "Add";
+const char MyMoneySplit::ActionSplitShares[] = "Split";
+
+MyMoneySplit::MyMoneySplit()
+{
+ m_reconcileFlag = NotReconciled;
+}
+
+MyMoneySplit::MyMoneySplit(const QDomElement& node) :
+ MyMoneyObject(node, false),
+ MyMoneyKeyValueContainer(node.elementsByTagName("KEYVALUEPAIRS").item(0).toElement())
+{
+ if("SPLIT" != node.tagName())
+ throw new MYMONEYEXCEPTION("Node was not SPLIT");
+
+ clearId();
+
+ m_payee = QStringEmpty(node.attribute("payee"));
+ 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"));
+}
+
+MyMoneySplit::MyMoneySplit(const QString& id, const MyMoneySplit& right) :
+ MyMoneyObject(id)
+{
+ *this = right;
+ setId(id);
+}
+
+MyMoneySplit::~MyMoneySplit()
+{
+}
+
+bool MyMoneySplit::operator == (const MyMoneySplit& right) const
+{
+ return MyMoneyObject::operator==(right) &&
+ MyMoneyKeyValueContainer::operator==(right) &&
+ m_account == right.m_account &&
+ m_payee == right.m_payee &&
+ m_memo == right.m_memo &&
+ m_action == right.m_action &&
+ m_reconcileDate == right.m_reconcileDate &&
+ m_reconcileFlag == right.m_reconcileFlag &&
+ ((m_number.length() == 0 && right.m_number.length() == 0) || m_number == right.m_number) &&
+ m_shares == right.m_shares &&
+ m_value == right.m_value &&
+ m_price == right.m_price &&
+ m_transactionId == right.m_transactionId;
+}
+
+void MyMoneySplit::setAccountId(const QString& account)
+{
+ m_account = account;
+}
+
+void MyMoneySplit::setMemo(const QString& memo)
+{
+ m_memo = memo;
+}
+
+void MyMoneySplit::setReconcileDate(const QDate& date)
+{
+ m_reconcileDate = date;
+}
+
+void MyMoneySplit::setReconcileFlag(const reconcileFlagE flag)
+{
+ m_reconcileFlag = flag;
+}
+
+void MyMoneySplit::setShares(const MyMoneyMoney& shares)
+{
+ m_shares = shares;
+}
+
+void MyMoneySplit::setValue(const MyMoneyMoney& value)
+{
+ m_value = value;
+}
+
+void MyMoneySplit::setValue(const MyMoneyMoney& value, const QString& transactionCurrencyId, const QString& splitCurrencyId)
+{
+ if(transactionCurrencyId == splitCurrencyId)
+ setValue(value);
+ else
+ setShares(value);
+}
+
+void MyMoneySplit::setPayeeId(const QString& payee)
+{
+ m_payee = payee;
+}
+
+void MyMoneySplit::setAction(investTransactionTypeE type)
+{
+ switch(type) {
+ case BuyShares:
+ case SellShares:
+ setAction(ActionBuyShares);
+ break;
+ case Dividend:
+ setAction(ActionDividend);
+ break;
+ case Yield:
+ setAction(ActionYield);
+ break;
+ case ReinvestDividend:
+ setAction(ActionReinvestDividend);
+ break;
+ case AddShares:
+ case RemoveShares:
+ setAction(ActionAddShares);
+ break;
+ case MyMoneySplit::SplitShares:
+ setAction(ActionSplitShares);
+ break;
+ case MyMoneySplit::UnknownTransactionType:
+ break;
+ }
+}
+
+void MyMoneySplit::setAction(const QString& action)
+{
+ m_action = action;
+}
+
+void MyMoneySplit::setNumber(const QString& number)
+{
+ m_number = number;
+}
+
+const MyMoneyMoney MyMoneySplit::value(const QString& transactionCurrencyId, const QString& splitCurrencyId) const
+{
+ return (transactionCurrencyId == splitCurrencyId) ? m_value : m_shares;
+}
+
+void MyMoneySplit::setPrice(const MyMoneyMoney& price)
+{
+ m_price = price;
+}
+
+MyMoneyMoney MyMoneySplit::price(void) const
+{
+ if(!m_price.isZero())
+ return m_price;
+ if(!m_value.isZero() && !m_shares.isZero())
+ return m_value / m_shares;
+ return MyMoneyMoney(1,1);
+}
+
+void MyMoneySplit::writeXML(QDomDocument& document, QDomElement& parent) const
+{
+ QDomElement el = document.createElement("SPLIT");
+
+ writeBaseXML(document, el);
+
+ el.setAttribute("payee", m_payee);
+ el.setAttribute("reconciledate", dateToString(m_reconcileDate));
+ el.setAttribute("action", m_action);
+ el.setAttribute("reconcileflag", m_reconcileFlag);
+ el.setAttribute("value", m_value.toString());
+ el.setAttribute("shares", m_shares.toString());
+ if(!m_price.isZero())
+ el.setAttribute("price", m_price.toString());
+ el.setAttribute("memo", m_memo);
+ // No need to write the split id as it will be re-assigned when the file is read
+ // el.setAttribute("id", split.id());
+ el.setAttribute("account", m_account);
+ el.setAttribute("number", m_number);
+ el.setAttribute("bankid", m_bankID);
+
+ MyMoneyKeyValueContainer::writeXML(document, el);
+
+ parent.appendChild(el);
+}
+
+bool MyMoneySplit::hasReferenceTo(const QString& id) const
+{
+ bool rc = false;
+ if(isMatched()) {
+ rc = matchedTransaction().hasReferenceTo(id);
+ }
+ return rc || (id == m_account) || (id == m_payee);
+}
+
+bool MyMoneySplit::isMatched(void) const
+{
+ return !(value("kmm-matched-tx").isEmpty());
+}
+
+void MyMoneySplit::addMatch(const MyMoneyTransaction& _t)
+{
+ if(_t.isImported() && !isMatched()) {
+ MyMoneyTransaction t(_t);
+ t.clearId();
+ QDomDocument doc("MATCH");
+ QDomElement el = doc.createElement("CONTAINER");
+ doc.appendChild(el);
+ t.writeXML(doc, el);
+ QString xml = doc.toString();
+ xml.replace("<", "&lt;");
+ setValue("kmm-matched-tx", xml);
+ }
+}
+
+void MyMoneySplit::removeMatch(void)
+{
+ deletePair("kmm-matched-tx");
+}
+
+MyMoneyTransaction MyMoneySplit::matchedTransaction(void) const
+{
+ QString xml = value("kmm-matched-tx");
+ if(!xml.isEmpty()) {
+ xml.replace("&lt;", "<");
+ QDomDocument doc;
+ QDomElement node;
+ doc.setContent(xml);
+ node = doc.documentElement().firstChild().toElement();
+ MyMoneyTransaction t(node, false);
+ return t;
+ }
+ return MyMoneyTransaction();
+}
+
+// vim:cin:si:ai:et:ts=2:sw=2:
diff --git a/kmymoney2/mymoney/mymoneysplit.h b/kmymoney2/mymoney/mymoneysplit.h
new file mode 100644
index 0000000..3408a6a
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneysplit.h
@@ -0,0 +1,307 @@
+/***************************************************************************
+ mymoneysplit.h - description
+ -------------------
+ begin : Sun Apr 28 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYSPLIT_H
+#define MYMONEYSPLIT_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstring.h>
+#include <qdatetime.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "mymoneyexception.h"
+#include "mymoneyutils.h"
+#include "mymoneymoney.h"
+#include <kmymoney/export.h>
+#include <kmymoney/mymoneyobject.h>
+#include <kmymoney/mymoneykeyvaluecontainer.h>
+class MyMoneyTransaction;
+
+/**
+ * @author Thomas Baumgart
+ */
+
+/**
+ * This class represents a split of a transaction.
+ */
+class KMYMONEY_EXPORT MyMoneySplit : public MyMoneyObject, public MyMoneyKeyValueContainer
+{
+public:
+ /**
+ * This enum defines the possible reconciliation states a split
+ * can be in. Possible values are as follows:
+ *
+ * @li NotReconciled
+ * @li Cleared
+ * @li Reconciled
+ * @li Frozen
+ *
+ * Whenever a new split is created, it has the status NotReconciled. It
+ * can be set to cleared when the transaction has been performed. Once the
+ * account is reconciled, cleared splits will be set to Reconciled. The
+ * state Frozen will be used, when the concept of books is introduced into
+ * the engine and a split must not be changed anymore.
+ */
+ enum reconcileFlagE {
+ Unknown = -1,
+ NotReconciled = 0,
+ Cleared,
+ Reconciled,
+ Frozen,
+ // insert new values above
+ MaxReconcileState
+ };
+
+ typedef enum {
+ UnknownTransactionType = -1,
+ BuyShares = 0,
+ SellShares,
+ Dividend,
+ ReinvestDividend,
+ Yield,
+ AddShares,
+ RemoveShares,
+ SplitShares
+ } investTransactionTypeE;
+
+ MyMoneySplit();
+ MyMoneySplit(const QDomElement& node);
+ MyMoneySplit(const QString& id, const MyMoneySplit& right);
+ ~MyMoneySplit();
+
+ bool operator == (const MyMoneySplit&) const;
+
+ void writeXML(QDomDocument& document, QDomElement& parent) const;
+
+ /**
+ * This method checks if a reference to the given object exists. It returns,
+ * a @p true if the object is referencing the one requested by the
+ * parameter @p id. If it does not, this method returns @p false.
+ *
+ * @param id id of the object to be checked for references
+ * @retval true This object references object with id @p id.
+ * @retval false This object does not reference the object with id @p id.
+ */
+ virtual bool hasReferenceTo(const QString& id) const;
+
+ const MyMoneyMoney& shares(void) const { return m_shares; }
+ const MyMoneyMoney& value(void) const { return m_value; }
+
+ /**
+ * This method returns the price. If the member m_price is not zero
+ * its value is returned. Otherwise, if m_shares is not zero the quotient
+ * of m_value / m_shares is returned. If m_values equals to zero, 1
+ * will be returned.
+ */
+ MyMoneyMoney price(void) const;
+ /** This method just returns what is in m_price, so when we write to the
+ * database, we don't just generate prices
+ */
+ MyMoneyMoney actualPrice(void) const { return m_price; }
+
+ const MyMoneyMoney value(const QString& transactionCurrencyId, const QString& splitCurrencyId) const;
+
+ /**
+ * Required to have (direct) access to the MyMoneyKeyValueContainer::value() method.
+ */
+ const QString& value(const QString& key) const { return MyMoneyKeyValueContainer::value(key); }
+
+ /**
+ * Required to have (direct) access to the MyMoneyKeyValueContainer::setValue() method.
+ */
+ void setValue(const QString& key, const QString& value) { MyMoneyKeyValueContainer::setValue(key, value); }
+
+ const QString& accountId(void) const { return m_account; }
+ const QString& memo(void) const { return m_memo; }
+ reconcileFlagE reconcileFlag(void) const { return m_reconcileFlag; }
+ const QDate& reconcileDate(void) const { return m_reconcileDate; }
+ const QString& payeeId(void) const { return m_payee; }
+ const QString& action(void) const { return m_action; }
+ const QString& number(void) const { return m_number; }
+ bool isAmortizationSplit(void) const { return m_action == ActionAmortization; }
+ bool isInterestSplit(void) const { return m_action == ActionInterest; }
+ bool isAutoCalc(void) const { return (m_shares == MyMoneyMoney::autoCalc) || (m_value == MyMoneyMoney::autoCalc); }
+ const QString& bankID(void) const { return m_bankID; }
+ const QString& transactionId(void) const { return m_transactionId; }
+
+ void setShares(const MyMoneyMoney& shares);
+ void setValue(const MyMoneyMoney& value);
+ void setPrice(const MyMoneyMoney& price);
+
+ /**
+ * This method is used to set either the shares or the value depending on
+ * the currencies assigned to the split/account and the transaction.
+ *
+ * If @p transactionCurrencyId equals @p splitCurrencyId this method
+ * calls setValue(MyMoneyMoney) otherwise setShares(MyMoneyMoney).
+ *
+ * @param value the value to be assiged
+ * @param transactionCurrencyId the id of the currency assigned to the transaction
+ * @param splitCurrencyId the id of the currency assigned to the split (i.e. the
+ * the id of the currency assigned to the account that is
+ * referenced by the split)
+ */
+ void setValue(const MyMoneyMoney& value, const QString& transactionCurrencyId, const QString& splitCurrencyId);
+
+ void setAccountId(const QString& account);
+ void setMemo(const QString& memo);
+ void setReconcileFlag(const reconcileFlagE flag);
+ void setReconcileDate(const QDate& date);
+ void setPayeeId(const QString& payee);
+ void setAction(const QString& action);
+ void setAction(investTransactionTypeE type);
+ void setNumber(const QString& number);
+ void setBankID(const QString& bankID) { m_bankID = bankID; };
+ void setTransactionId(const QString& id) { m_transactionId = id; }
+
+ /**
+ * returns @a true if this its a transaction matched against an imported
+ * transaction. The imported and matched transaction can be extracted
+ * using matchedTransaction() and can be removed using removeMatch().
+ */
+ bool isMatched(void) const;
+
+ /**
+ * add an imported transaction @p t as matching transaction. Any previously
+ * added transaction will be overridden. @p t.isImported() must return true,
+ * otherwise the transaction is not stored.
+ */
+ void addMatch(const MyMoneyTransaction& t);
+
+ /**
+ * remove the data of the imported transaction added with addMatch().
+ */
+ void removeMatch(void);
+
+ /**
+ * Return the matching imported transaction. If no such transaction
+ * is available (isMatched() returns false) an empty transaction is returned.
+ */
+ MyMoneyTransaction matchedTransaction(void) const;
+
+ static const char ActionCheck[];
+ static const char ActionDeposit[];
+ static const char ActionTransfer[];
+ static const char ActionWithdrawal[];
+ static const char ActionATM[];
+
+ static const char ActionAmortization[];
+ static const char ActionInterest[];
+
+ static const char ActionBuyShares[]; // negative amount is sellShares
+ static const char ActionDividend[];
+ static const char ActionReinvestDividend[];
+ static const char ActionYield[];
+ static const char ActionAddShares[]; // negative amount is removeShares
+ static const char ActionSplitShares[];
+
+private:
+ /**
+ * This member contains the ID of the transaction
+ */
+ QString m_id;
+
+ /**
+ * This member contains the ID of the payee
+ */
+ QString m_payee;
+
+ /**
+ * This member contains the ID of the account
+ */
+ QString m_account;
+
+ /**
+ */
+ MyMoneyMoney m_shares;
+
+ /**
+ */
+ MyMoneyMoney m_value;
+
+
+ /**
+ * If the quotient of m_shares divided by m_values is not the correct price
+ * because of truncation, the price can be stored in this member. For display
+ * purpose and transaction edit this value can be used by the application.
+ */
+ MyMoneyMoney m_price;
+
+ QString m_memo;
+
+ /**
+ * This member contains information about the reconciliation
+ * state of the split. Possible values are
+ *
+ * @li NotReconciled
+ * @li Cleared
+ * @li Reconciled
+ * @li Frozen
+ *
+ */
+ reconcileFlagE m_reconcileFlag;
+
+ /**
+ * In case the reconciliation flag is set to Reconciled or Frozen
+ * this member contains the date of the reconciliation.
+ */
+ QDate m_reconcileDate;
+
+ /**
+ * The m_action member is an arbitrary string, but is intended to
+ * be conveniently limited to a menu of selections such as
+ * "Buy", "Sell", "Interest", etc.
+ */
+ QString m_action;
+
+ /**
+ * The m_number member is used to store a reference number to
+ * the split supplied by the user (e.g. check number, etc.).
+ */
+ QString m_number;
+
+ /**
+ * This member keeps the bank's unique ID for the split, so we can
+ * avoid duplicates. This is only used for electronic statement downloads.
+ *
+ * This should only be set on the split which refers to the account
+ * that was downloaded.
+ */
+ QString m_bankID;
+
+ /**
+ * This member keeps a backward id to the transaction that this
+ * split can be found in. It is the purpose of the MyMoneyTransaction
+ * object to maintain this member variable.
+ */
+ QString m_transactionId;
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneysplittest.cpp b/kmymoney2/mymoney/mymoneysplittest.cpp
new file mode 100644
index 0000000..a592e1a
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneysplittest.cpp
@@ -0,0 +1,306 @@
+/***************************************************************************
+ mymoneysplittest.cpp
+ -------------------
+ copyright : (C) 2002 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "mymoneysplittest.h"
+#include <kmymoney/mymoneyexception.h>
+
+MyMoneySplitTest::MyMoneySplitTest()
+{
+}
+
+
+void MyMoneySplitTest::setUp () {
+ m = new MyMoneySplit();
+}
+
+void MyMoneySplitTest::tearDown () {
+ delete m;
+}
+
+void MyMoneySplitTest::testEmptyConstructor() {
+ CPPUNIT_ASSERT(m->accountId().isEmpty());
+ CPPUNIT_ASSERT(m->id().isEmpty());
+ CPPUNIT_ASSERT(m->memo().isEmpty());
+ CPPUNIT_ASSERT(m->action().isEmpty());
+ CPPUNIT_ASSERT(m->shares().isZero());
+ CPPUNIT_ASSERT(m->value().isZero());
+ CPPUNIT_ASSERT(m->reconcileFlag() == MyMoneySplit::NotReconciled);
+ CPPUNIT_ASSERT(m->reconcileDate() == QDate());
+ CPPUNIT_ASSERT(m->transactionId().isEmpty());
+}
+
+void MyMoneySplitTest::testSetFunctions() {
+ m->setAccountId("Account");
+ m->setMemo("Memo");
+ m->setReconcileDate(QDate(1,2,3));
+ m->setReconcileFlag(MyMoneySplit::Cleared);
+ m->setShares(1234);
+ m->setValue(3456);
+ m->setId("MyID");
+ m->setPayeeId("Payee");
+ m->setAction("Action");
+ m->setTransactionId("TestTransaction");
+ m->setValue("Key", "Value");
+
+ CPPUNIT_ASSERT(m->accountId() == "Account");
+ CPPUNIT_ASSERT(m->memo() == "Memo");
+ CPPUNIT_ASSERT(m->reconcileDate() == QDate(1,2,3));
+ CPPUNIT_ASSERT(m->reconcileFlag() == MyMoneySplit::Cleared);
+ CPPUNIT_ASSERT(m->shares() == MyMoneyMoney(1234));
+ CPPUNIT_ASSERT(m->value() == MyMoneyMoney(3456));
+ CPPUNIT_ASSERT(m->id() == "MyID");
+ CPPUNIT_ASSERT(m->payeeId() == "Payee");
+ CPPUNIT_ASSERT(m->action() == "Action");
+ CPPUNIT_ASSERT(m->transactionId() == "TestTransaction");
+ CPPUNIT_ASSERT(m->value("Key") == "Value");
+}
+
+
+void MyMoneySplitTest::testCopyConstructor() {
+ testSetFunctions();
+
+ MyMoneySplit n(*m);
+
+ CPPUNIT_ASSERT(n.accountId() == "Account");
+ CPPUNIT_ASSERT(n.memo() == "Memo");
+ CPPUNIT_ASSERT(n.reconcileDate() == QDate(1,2,3));
+ CPPUNIT_ASSERT(n.reconcileFlag() == MyMoneySplit::Cleared);
+ CPPUNIT_ASSERT(n.shares() == MyMoneyMoney(1234));
+ CPPUNIT_ASSERT(n.value() == MyMoneyMoney(3456));
+ CPPUNIT_ASSERT(n.id() == "MyID");
+ CPPUNIT_ASSERT(n.payeeId() == "Payee");
+ CPPUNIT_ASSERT(n.action() == "Action");
+ CPPUNIT_ASSERT(n.transactionId() == "TestTransaction");
+ CPPUNIT_ASSERT(n.value("Key") == "Value");
+}
+
+void MyMoneySplitTest::testAssignmentConstructor() {
+ testSetFunctions();
+
+ MyMoneySplit n;
+
+ n = *m;
+
+ CPPUNIT_ASSERT(n.accountId() == "Account");
+ CPPUNIT_ASSERT(n.memo() == "Memo");
+ CPPUNIT_ASSERT(n.reconcileDate() == QDate(1,2,3));
+ CPPUNIT_ASSERT(n.reconcileFlag() == MyMoneySplit::Cleared);
+ CPPUNIT_ASSERT(n.shares() == MyMoneyMoney(1234));
+ CPPUNIT_ASSERT(n.value() == MyMoneyMoney(3456));
+ CPPUNIT_ASSERT(n.id() == "MyID");
+ CPPUNIT_ASSERT(n.payeeId() == "Payee");
+ CPPUNIT_ASSERT(n.action() == "Action");
+ CPPUNIT_ASSERT(n.transactionId() == "TestTransaction");
+ CPPUNIT_ASSERT(n.value("Key") == "Value");
+}
+
+void MyMoneySplitTest::testEquality() {
+ testSetFunctions();
+
+ MyMoneySplit n(*m);
+
+ CPPUNIT_ASSERT(n == *m);
+}
+
+void MyMoneySplitTest::testInequality() {
+ testSetFunctions();
+
+ MyMoneySplit n(*m);
+
+ n.setShares(3456);
+ CPPUNIT_ASSERT(!(n == *m));
+
+ n = *m;
+ n.setId("Not My ID");
+ CPPUNIT_ASSERT(!(n == *m));
+
+ n = *m;
+ n.setPayeeId("No payee");
+ CPPUNIT_ASSERT(!(n == *m));
+
+ n = *m;
+ n.setAction("No action");
+ CPPUNIT_ASSERT(!(n == *m));
+
+ n = *m;
+ n.setNumber("No number");
+ CPPUNIT_ASSERT(!(n == *m));
+
+ n = *m;
+ n.setAccountId("No account");
+ CPPUNIT_ASSERT(!(n == *m));
+
+ n = *m;
+ n.setMemo("No memo");
+ CPPUNIT_ASSERT(!(n == *m));
+
+ n = *m;
+ n.setReconcileDate(QDate(3,4,5));
+ CPPUNIT_ASSERT(!(n == *m));
+
+ n = *m;
+ n.setReconcileFlag(MyMoneySplit::Frozen);
+ CPPUNIT_ASSERT(!(n == *m));
+
+ n = *m;
+ n.setShares(4567);
+ CPPUNIT_ASSERT(!(n == *m));
+
+ n = *m;
+ n.setValue(9876);
+ CPPUNIT_ASSERT(!(n == *m));
+
+ n = *m;
+ n.setTransactionId("NoTransaction");
+ CPPUNIT_ASSERT(!(n == *m));
+
+ n = *m;
+ n.setValue("Key", "NoValue");
+ CPPUNIT_ASSERT(!(n == *m));
+}
+
+
+void MyMoneySplitTest::testAmortization() {
+ CPPUNIT_ASSERT(m->isAmortizationSplit() == false);
+ testSetFunctions();
+ CPPUNIT_ASSERT(m->isAmortizationSplit() == false);
+ m->setAction(MyMoneySplit::ActionAmortization);
+ CPPUNIT_ASSERT(m->isAmortizationSplit() == true);
+}
+
+void MyMoneySplitTest::testValue() {
+ m->setValue(1);
+ m->setShares(2);
+ CPPUNIT_ASSERT(m->value("EUR", "EUR") == MyMoneyMoney(1));
+ CPPUNIT_ASSERT(m->value("EUR", "USD") == MyMoneyMoney(2));
+}
+
+void MyMoneySplitTest::testSetValue() {
+ CPPUNIT_ASSERT(m->value().isZero());
+ CPPUNIT_ASSERT(m->shares().isZero());
+ m->setValue(1, "EUR", "EUR");
+ CPPUNIT_ASSERT(m->value() == MyMoneyMoney(1));
+ CPPUNIT_ASSERT(m->shares().isZero());
+ m->setValue(3, "EUR", "USD");
+ CPPUNIT_ASSERT(m->value() == MyMoneyMoney(1));
+ CPPUNIT_ASSERT(m->shares() == MyMoneyMoney(3));
+}
+
+void MyMoneySplitTest::testSetAction() {
+ CPPUNIT_ASSERT(m->action() == QString());
+ m->setAction(MyMoneySplit::BuyShares);
+ CPPUNIT_ASSERT(m->action() == MyMoneySplit::ActionBuyShares);
+ m->setAction(MyMoneySplit::SellShares);
+ CPPUNIT_ASSERT(m->action() == MyMoneySplit::ActionBuyShares);
+ m->setAction(MyMoneySplit::Dividend);
+ CPPUNIT_ASSERT(m->action() == MyMoneySplit::ActionDividend);
+ m->setAction(MyMoneySplit::Yield);
+ CPPUNIT_ASSERT(m->action() == MyMoneySplit::ActionYield);
+ m->setAction(MyMoneySplit::ReinvestDividend);
+ CPPUNIT_ASSERT(m->action() == MyMoneySplit::ActionReinvestDividend);
+ m->setAction(MyMoneySplit::AddShares);
+ CPPUNIT_ASSERT(m->action() == MyMoneySplit::ActionAddShares);
+ m->setAction(MyMoneySplit::RemoveShares);
+ CPPUNIT_ASSERT(m->action() == MyMoneySplit::ActionAddShares);
+ m->setAction(MyMoneySplit::SplitShares);
+ CPPUNIT_ASSERT(m->action() == MyMoneySplit::ActionSplitShares);
+}
+
+void MyMoneySplitTest::testIsAutoCalc() {
+ CPPUNIT_ASSERT(m->isAutoCalc() == false);
+ m->setValue(MyMoneyMoney::autoCalc);
+ CPPUNIT_ASSERT(m->isAutoCalc() == true);
+ m->setShares(MyMoneyMoney::autoCalc);
+ CPPUNIT_ASSERT(m->isAutoCalc() == true);
+ m->setValue(0);
+ CPPUNIT_ASSERT(m->isAutoCalc() == true);
+ m->setShares(1);
+ CPPUNIT_ASSERT(m->isAutoCalc() == false);
+}
+
+void MyMoneySplitTest::testWriteXML() {
+ MyMoneySplit s;
+
+ s.setPayeeId("P000001");
+ s.setShares(MyMoneyMoney(96379, 100));
+ s.setValue(MyMoneyMoney(96379, 1000));
+ s.setAccountId("A000076");
+ s.setNumber("124");
+ s.setBankID("SPID");
+ s.setAction(MyMoneySplit::ActionDeposit);
+ s.setReconcileFlag(MyMoneySplit::Reconciled);
+
+ QDomDocument doc("TEST");
+ QDomElement el = doc.createElement("SPLIT-CONTAINER");
+ doc.appendChild(el);
+ s.writeXML(doc, el);
+
+ QString ref = QString(
+ "<!DOCTYPE TEST>\n"
+ "<SPLIT-CONTAINER>\n"
+ " <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"96379/100\" action=\"Deposit\" bankid=\"SPID\" number=\"124\" reconcileflag=\"2\" memo=\"\" value=\"96379/1000\" id=\"\" account=\"A000076\" />\n"
+ "</SPLIT-CONTAINER>\n");
+
+ CPPUNIT_ASSERT(doc.toString() == ref);
+}
+
+void MyMoneySplitTest::testReadXML() {
+ MyMoneySplit s;
+ QString ref_ok = QString(
+ "<!DOCTYPE TEST>\n"
+ "<SPLIT-CONTAINER>\n"
+ " <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"96379/100\" action=\"Deposit\" bankid=\"SPID\" number=\"124\" reconcileflag=\"2\" memo=\"MyMemo\" value=\"96379/1000\" account=\"A000076\" />\n"
+ "</SPLIT-CONTAINER>\n");
+
+ QString ref_false = QString(
+ "<!DOCTYPE TEST>\n"
+ "<SPLIT-CONTAINER>\n"
+ " <SPLITS payee=\"P000001\" reconciledate=\"\" shares=\"96379/100\" action=\"Deposit\" bankid=\"SPID\" number=\"124\" reconcileflag=\"2\" memo=\"\" value=\"96379/1000\" account=\"A000076\" />\n"
+ "</SPLIT-CONTAINER>\n");
+
+ QDomDocument doc;
+ QDomElement node;
+ doc.setContent(ref_false);
+ node = doc.documentElement().firstChild().toElement();
+
+ try {
+ s = MyMoneySplit(node);
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+
+ doc.setContent(ref_ok);
+ node = doc.documentElement().firstChild().toElement();
+
+ try {
+ s = MyMoneySplit(node);
+ CPPUNIT_ASSERT(s.id().isEmpty());
+ CPPUNIT_ASSERT(s.payeeId() == "P000001");
+ CPPUNIT_ASSERT(s.reconcileDate() == QDate());
+ CPPUNIT_ASSERT(s.shares() == MyMoneyMoney(96379, 100));
+ CPPUNIT_ASSERT(s.value() == MyMoneyMoney(96379, 1000));
+ CPPUNIT_ASSERT(s.number() == "124");
+ CPPUNIT_ASSERT(s.bankID() == "SPID");
+ CPPUNIT_ASSERT(s.reconcileFlag() == MyMoneySplit::Reconciled);
+ CPPUNIT_ASSERT(s.action() == MyMoneySplit::ActionDeposit);
+ CPPUNIT_ASSERT(s.accountId() == "A000076");
+ CPPUNIT_ASSERT(s.memo() == "MyMemo");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+
+}
diff --git a/kmymoney2/mymoney/mymoneysplittest.h b/kmymoney2/mymoney/mymoneysplittest.h
new file mode 100644
index 0000000..d54e11e
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneysplittest.h
@@ -0,0 +1,67 @@
+/***************************************************************************
+ mymoneysplittest.h
+ -------------------
+ copyright : (C) 2002 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 __MYMONEYSPLITTEST_H__
+#define __MYMONEYSPLITTEST_H__
+
+#include <cppunit/extensions/HelperMacros.h>
+
+#define private public
+#define protected public
+#include "mymoneysplit.h"
+#undef private
+
+class MyMoneySplitTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(MyMoneySplitTest);
+ CPPUNIT_TEST(testEmptyConstructor);
+ CPPUNIT_TEST(testSetFunctions);
+ CPPUNIT_TEST(testCopyConstructor);
+ CPPUNIT_TEST(testAssignmentConstructor);
+ CPPUNIT_TEST(testEquality);
+ CPPUNIT_TEST(testInequality);
+ CPPUNIT_TEST(testAmortization);
+ CPPUNIT_TEST(testValue);
+ CPPUNIT_TEST(testSetValue);
+ CPPUNIT_TEST(testSetAction);
+ CPPUNIT_TEST(testIsAutoCalc);
+ CPPUNIT_TEST(testWriteXML);
+ CPPUNIT_TEST(testReadXML);
+ CPPUNIT_TEST_SUITE_END();
+
+protected:
+ MyMoneySplit *m;
+
+public:
+ MyMoneySplitTest ();
+
+ void setUp ();
+ void tearDown ();
+ void testEmptyConstructor();
+ void testSetFunctions();
+ void testCopyConstructor();
+ void testAssignmentConstructor();
+ void testEquality();
+ void testInequality();
+ void testAmortization();
+ void testValue();
+ void testSetValue();
+ void testSetAction();
+ void testIsAutoCalc();
+ void testWriteXML();
+ void testReadXML();
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneystatement.cpp b/kmymoney2/mymoney/mymoneystatement.cpp
new file mode 100644
index 0000000..0373863
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneystatement.cpp
@@ -0,0 +1,299 @@
+/***************************************************************************
+ mymoneystatement.cpp
+ -------------------
+ begin : Mon Aug 30 2004
+ copyright : (C) 2000-2004 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ Ace Jones <acejones@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qdom.h>
+#include <qstringlist.h>
+#include <qfile.h>
+#include <qtextstream.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "../../kdecompat.h"
+#include "mymoneystatement.h"
+
+const QStringList kAccountTypeTxt = QStringList::split(",","none,checkings,savings,investment,creditcard,invalid");
+const QStringList kActionText = QStringList::split(",","none,buy,sell,reinvestdividend,cashdividend,add,remove,stocksplit,fees,interest,invalid");
+
+void MyMoneyStatement::write(QDomElement& _root,QDomDocument* _doc) const
+{
+ QDomElement e = _doc->createElement("STATEMENT");
+ _root.appendChild(e);
+
+ e.setAttribute("version","1.1");
+ e.setAttribute("accountname", m_strAccountName);
+ e.setAttribute("accountnumber", m_strAccountNumber);
+ e.setAttribute("routingnumber", m_strRoutingNumber);
+ e.setAttribute("currency", m_strCurrency);
+ e.setAttribute("begindate", m_dateBegin.toString(Qt::ISODate));
+ e.setAttribute("enddate", m_dateEnd.toString(Qt::ISODate));
+ e.setAttribute("closingbalance", m_closingBalance.toString());
+ e.setAttribute("type", kAccountTypeTxt[m_eType]);
+ e.setAttribute("accountid", m_accountId);
+ e.setAttribute("skipCategoryMatching", m_skipCategoryMatching);
+
+ // iterate over transactions, and add each one
+ QValueList<Transaction>::const_iterator it_t = m_listTransactions.begin();
+ while ( it_t != m_listTransactions.end() )
+ {
+ QDomElement p = _doc->createElement("TRANSACTION");
+ p.setAttribute("dateposted", (*it_t).m_datePosted.toString(Qt::ISODate));
+ p.setAttribute("payee", (*it_t).m_strPayee);
+ p.setAttribute("memo", (*it_t).m_strMemo);
+ p.setAttribute("number", (*it_t).m_strNumber);
+ p.setAttribute("amount", (*it_t).m_amount.toString());
+ p.setAttribute("bankid", (*it_t).m_strBankID);
+ p.setAttribute("reconcile", (*it_t).m_reconcile);
+ p.setAttribute("action", kActionText[(*it_t).m_eAction]);
+
+ if (m_eType == etInvestment)
+ {
+ p.setAttribute("shares", (*it_t).m_shares.toString());
+ p.setAttribute("security", (*it_t).m_strSecurity);
+ p.setAttribute("brokerageaccount", (*it_t).m_strBrokerageAccount);
+ }
+
+ // add all the splits we know of (might be empty)
+ QValueList<Split>::const_iterator it_s;
+ for(it_s = (*it_t).m_listSplits.begin(); it_s != (*it_t).m_listSplits.end(); ++it_s) {
+ QDomElement split = _doc->createElement("SPLIT");
+ split.setAttribute("accountid", (*it_s).m_accountId);
+ split.setAttribute("amount", (*it_s).m_amount.toString());
+ split.setAttribute("reconcile", (*it_s).m_reconcile);
+ split.setAttribute("category", (*it_s).m_strCategoryName);
+ split.setAttribute("memo", (*it_s).m_strMemo);
+ split.setAttribute("reconcile", (*it_s).m_reconcile);
+ p.appendChild(split);
+ }
+
+ e.appendChild(p);
+
+ ++it_t;
+ }
+
+ // iterate over prices, and add each one
+ QValueList<Price>::const_iterator it_p = m_listPrices.begin();
+ while ( it_p != m_listPrices.end() )
+ {
+ QDomElement p = _doc->createElement("PRICE");
+ p.setAttribute("dateposted", (*it_p).m_date.toString(Qt::ISODate));
+ p.setAttribute("security", (*it_p).m_strSecurity);
+ p.setAttribute("amount", (*it_p).m_amount.toString());
+
+ e.appendChild(p);
+
+ ++it_p;
+ }
+
+ // iterate over securities, and add each one
+ QValueList<Security>::const_iterator it_s = m_listSecurities.begin();
+ while ( it_s != m_listSecurities.end() )
+ {
+ QDomElement p = _doc->createElement("SECURITY");
+ p.setAttribute("name", (*it_s).m_strName);
+ p.setAttribute("symbol", (*it_s).m_strSymbol);
+ p.setAttribute("id", (*it_s).m_strId);
+
+ e.appendChild(p);
+
+ ++it_s;
+ }
+
+}
+
+bool MyMoneyStatement::read(const QDomElement& _e)
+{
+ bool result = false;
+
+ if ( _e.tagName() == "STATEMENT" )
+ {
+ result = true;
+
+ m_strAccountName = _e.attribute("accountname");
+ m_strAccountNumber = _e.attribute("accountnumber");
+ m_strRoutingNumber = _e.attribute("routingnumber");
+ m_strCurrency = _e.attribute("currency");
+ m_dateBegin = QDate::fromString(_e.attribute("begindate"),Qt::ISODate);
+ m_dateEnd = QDate::fromString(_e.attribute("enddate"),Qt::ISODate);
+ m_closingBalance = MyMoneyMoney(_e.attribute("closingbalance"));
+ m_accountId = _e.attribute("accountid");
+ m_skipCategoryMatching = _e.attribute("skipCategoryMatching");
+
+ int i = kAccountTypeTxt.findIndex(_e.attribute("type",kAccountTypeTxt[1]));
+ if ( i != -1 )
+ m_eType = static_cast<EType>(i);
+
+ QDomNode child = _e.firstChild();
+ while(!child.isNull() && child.isElement())
+ {
+ QDomElement c = child.toElement();
+
+ if ( c.tagName() == "TRANSACTION" )
+ {
+ MyMoneyStatement::Transaction t;
+
+ t.m_datePosted = QDate::fromString(c.attribute("dateposted"),Qt::ISODate);
+ t.m_amount = MyMoneyMoney(c.attribute("amount"));
+ t.m_strMemo = c.attribute("memo");
+ t.m_strNumber = c.attribute("number");
+ t.m_strPayee = c.attribute("payee");
+ t.m_strBankID = c.attribute("bankid");
+ t.m_reconcile = static_cast<MyMoneySplit::reconcileFlagE>(c.attribute("reconcile").toInt());
+ int i = kActionText.findIndex(c.attribute("action",kActionText[1]));
+ if ( i != -1 )
+ t.m_eAction = static_cast<Transaction::EAction>(i);
+
+ if (m_eType == etInvestment)
+ {
+ t.m_shares = MyMoneyMoney(c.attribute("shares"));
+ t.m_strSecurity = c.attribute("security");
+ t.m_strBrokerageAccount = c.attribute("brokerageaccount");
+ }
+
+ // process splits (if any)
+ QDomNode child = c.firstChild();
+ while(!child.isNull() && child.isElement()) {
+ QDomElement c = child.toElement();
+ if(c.tagName() == "SPLIT") {
+ MyMoneyStatement::Split s;
+ s.m_accountId = c.attribute("accountid");
+ s.m_amount = MyMoneyMoney(c.attribute("amount"));
+ s.m_reconcile = static_cast<MyMoneySplit::reconcileFlagE>(c.attribute("reconcile").toInt());
+ s.m_strCategoryName = c.attribute("category");
+ s.m_strMemo = c.attribute("memo");
+ t.m_listSplits += s;
+ }
+ child = child.nextSibling();
+ }
+ m_listTransactions += t;
+ }
+ else if ( c.tagName() == "PRICE" )
+ {
+ MyMoneyStatement::Price p;
+
+ p.m_date = QDate::fromString(c.attribute("dateposted"), Qt::ISODate);
+ p.m_strSecurity = c.attribute("security");
+ p.m_amount = MyMoneyMoney(c.attribute("amount"));
+
+ m_listPrices += p;
+ }
+ else if ( c.tagName() == "SECURITY" )
+ {
+ MyMoneyStatement::Security s;
+
+ s.m_strName = c.attribute("name");
+ s.m_strSymbol = c.attribute("symbol");
+ s.m_strId = c.attribute("id");
+
+ m_listSecurities += s;
+ }
+ child = child.nextSibling();
+ }
+ }
+
+ return result;
+}
+
+bool MyMoneyStatement::isStatementFile(const QString& _filename)
+{
+ // filename is considered a statement file if it contains
+ // the tag "<KMYMONEY2-STATEMENT>" in the first 20 lines.
+ bool result = false;
+
+ QFile f( _filename );
+ if ( f.open( IO_ReadOnly ) )
+ {
+ QTextStream ts( &f );
+
+ int lineCount = 20;
+ while ( !ts.atEnd() && !result && lineCount != 0) {
+ if ( ts.readLine().contains("<KMYMONEY-STATEMENT>",false) )
+ result = true;
+ --lineCount;
+ }
+ f.close();
+ }
+
+ return result;
+}
+
+void MyMoneyStatement::writeXMLFile( const MyMoneyStatement& _s, const QString& _filename )
+{
+ static unsigned filenum = 1;
+ QString filename = _filename;
+ if ( filename.isEmpty() ) {
+ filename = QString("statement-%1%2.xml").arg((filenum<10)?"0":"").arg(filenum);
+ filenum++;
+ }
+
+ QDomDocument* doc = new QDomDocument("KMYMONEY-STATEMENT");
+ Q_CHECK_PTR(doc);
+
+ //writeStatementtoXMLDoc(_s,doc);
+ QDomProcessingInstruction instruct = doc->createProcessingInstruction(QString("xml"), QString("version=\"1.0\" encoding=\"utf-8\""));
+ doc->appendChild(instruct);
+ QDomElement eroot = doc->createElement("KMYMONEY-STATEMENT");
+ doc->appendChild(eroot);
+ _s.write(eroot,doc);
+
+ QFile g( filename );
+ if(g.open( IO_WriteOnly )) {
+ QTextStream stream(&g);
+ stream.setEncoding(QTextStream::UnicodeUTF8);
+ stream << doc->toString();
+ g.close();
+ }
+
+ delete doc;
+}
+
+bool MyMoneyStatement::readXMLFile( MyMoneyStatement& _s, const QString& _filename )
+{
+ bool result = false;
+ QFile f( _filename );
+ f.open( IO_ReadOnly );
+ QDomDocument* doc = new QDomDocument;
+ if(doc->setContent(&f, false))
+ {
+ QDomElement rootElement = doc->documentElement();
+ if(!rootElement.isNull())
+ {
+ QDomNode child = rootElement.firstChild();
+ while(!child.isNull() && child.isElement())
+ {
+ result = true;
+ QDomElement childElement = child.toElement();
+ _s.read(childElement);
+
+ child = child.nextSibling();
+ }
+ }
+ }
+ delete doc;
+
+ return result;
+}
+// vim:cin:si:ai:et:ts=2:sw=2:
diff --git a/kmymoney2/mymoney/mymoneystatement.h b/kmymoney2/mymoney/mymoneystatement.h
new file mode 100644
index 0000000..0bea510
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneystatement.h
@@ -0,0 +1,145 @@
+/***************************************************************************
+ mymoneystatement.h
+ -------------------
+ begin : Mon Aug 30 2004
+ copyright : (C) 2000-2004 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ Ace Jones <acejones@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYSTATEMENT_H
+#define MYMONEYSTATEMENT_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstring.h>
+#include <qvaluelist.h>
+#include <qdatetime.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include <kmymoney/export.h>
+#include <kmymoney/mymoneymoney.h>
+#include <kmymoney/mymoneysplit.h>
+
+class QDomElement;
+class QDomDocument;
+
+/**
+Represents the electronic analog of the paper bank statement just like we used to get in the regular mail. This class is designed to be easy to extend and easy to create with minimal dependencies. So the header file should include as few project files as possible (preferrably NONE).
+
+@author ace jones
+*/
+class MyMoneyStatement
+{
+public:
+ MyMoneyStatement() : m_closingBalance(MyMoneyMoney::autoCalc), m_eType(etNone), m_skipCategoryMatching(false) {}
+
+ enum EType { etNone = 0, etCheckings, etSavings, etInvestment, etCreditCard, etEnd };
+
+ class Split {
+ public:
+ Split() : m_reconcile(MyMoneySplit::NotReconciled) {}
+ QString m_strCategoryName;
+ QString m_strMemo;
+ QString m_accountId;
+ MyMoneySplit::reconcileFlagE m_reconcile;
+ MyMoneyMoney m_amount;
+
+ };
+
+ class Transaction {
+ public:
+ Transaction() : m_reconcile(MyMoneySplit::NotReconciled), m_eAction(eaNone) {}
+ QDate m_datePosted;
+ QString m_strPayee;
+ QString m_strMemo;
+ QString m_strNumber;
+ QString m_strBankID;
+ MyMoneyMoney m_amount;
+ MyMoneySplit::reconcileFlagE m_reconcile;
+
+ // the following members are only used for investment accounts (m_eType==etInvestment)
+ // eaNone means the action, shares, and security can be ignored.
+ enum EAction { eaNone = 0, eaBuy, eaSell, eaReinvestDividend, eaCashDividend, eaShrsin, eaShrsout, eaStksplit, eaFees, eaInterest, eaEnd };
+ EAction m_eAction;
+ MyMoneyMoney m_shares;
+ MyMoneyMoney m_fees;
+ MyMoneyMoney m_price;
+ QString m_strInterestCategory;
+ QString m_strBrokerageAccount;
+ QString m_strSymbol;
+ QString m_strSecurity;
+ QValueList<Split> m_listSplits;
+ };
+
+ struct Price
+ {
+ QDate m_date;
+ QString m_strSecurity;
+ MyMoneyMoney m_amount;
+ };
+
+ struct Security
+ {
+ QString m_strName;
+ QString m_strSymbol;
+ QString m_strId;
+ };
+
+ QString m_strAccountName;
+ QString m_strAccountNumber;
+ QString m_strRoutingNumber;
+
+ /**
+ * The statement provider's information for the statement reader how to find the
+ * account. The provider usually leaves some value with a key unique to the provider in the KVP of the
+ * MyMoneyAccount object when setting up the connection or at a later point in time.
+ * Using the KMyMoneyPlugin::KMMStatementInterface::account() method it can retrieve the
+ * MyMoneyAccount object for this key. The account id of that account should be returned
+ * here. If no id is available, leave it empty.
+ */
+ QString m_accountId;
+
+ QString m_strCurrency;
+ QDate m_dateBegin;
+ QDate m_dateEnd;
+ MyMoneyMoney m_closingBalance;
+ EType m_eType;
+
+ QValueList<Transaction> m_listTransactions;
+ QValueList<Price> m_listPrices;
+ QValueList<Security> m_listSecurities;
+
+ bool m_skipCategoryMatching;
+
+ void write(QDomElement&,QDomDocument*) const;
+ bool read(const QDomElement&);
+
+ KMYMONEY_EXPORT static bool isStatementFile(const QString&);
+ KMYMONEY_EXPORT static bool readXMLFile( MyMoneyStatement&, const QString& );
+ KMYMONEY_EXPORT static void writeXMLFile( const MyMoneyStatement&, const QString& );
+};
+
+#endif
+// vim:cin:si:ai:et:ts=2:sw=2:
diff --git a/kmymoney2/mymoney/mymoneysubject.cpp b/kmymoney2/mymoney/mymoneysubject.cpp
new file mode 100644
index 0000000..c54d738
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneysubject.cpp
@@ -0,0 +1,58 @@
+/***************************************************************************
+ mymoneysubject.cpp - description
+ -------------------
+ begin : Sat May 18 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "mymoneysubject.h"
+#include "mymoneyobserver.h"
+#include <qptrvector.h>
+
+MyMoneySubject::MyMoneySubject()
+{
+}
+
+MyMoneySubject::~MyMoneySubject()
+{
+}
+
+void MyMoneySubject::attach (MyMoneyObserver* o)
+{
+ m_observers.append(o);
+}
+
+void MyMoneySubject::detach (MyMoneyObserver* o)
+{
+ m_observers.remove(o);
+}
+
+void MyMoneySubject::notify(const QString& id)
+{
+ QPtrList<MyMoneyObserver> ptrList = m_observers;
+ MyMoneyObserver* i;
+
+ for (i = ptrList.first(); i != 0; i = ptrList.next()) {
+ // only call the observer if it did not detach in the meantime
+ if(m_observers.findRef(i) != -1) {
+ // qDebug("call observer @ 0x%08lX with '%s'", (unsigned long)i, id.data());
+ i->update(id);
+ }
+ }
+}
+
diff --git a/kmymoney2/mymoney/mymoneysubject.h b/kmymoney2/mymoney/mymoneysubject.h
new file mode 100644
index 0000000..74b8290
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneysubject.h
@@ -0,0 +1,59 @@
+/***************************************************************************
+ mymoneysubject.h - description
+ -------------------
+ begin : Sat May 18 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYSUBJECT_H
+#define MYMONEYSUBJECT_H
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qptrlist.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include <kmymoney/export.h>
+
+class MyMoneyObserver;
+class QString;
+
+/**
+ * This is the base class to be used to construct a
+ * subject for usage in a subject/observer relationship
+ *
+ * @author Thomas Baumgart
+ */
+class KMYMONEY_EXPORT MyMoneySubject {
+public:
+ virtual ~MyMoneySubject();
+ virtual void attach(MyMoneyObserver*);
+ virtual void detach(MyMoneyObserver*);
+ virtual void notify(const QString& id);
+
+protected:
+ MyMoneySubject();
+
+private:
+ QPtrList<MyMoneyObserver> m_observers;
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneytransaction.cpp b/kmymoney2/mymoney/mymoneytransaction.cpp
new file mode 100644
index 0000000..10b4c08
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneytransaction.cpp
@@ -0,0 +1,484 @@
+/***************************************************************************
+
+ mymoneytransaction.cpp
+ -------------------
+ copyright : (C) 2000 by Michael Edwardes,
+ 2002 by Thomas Baumgart
+ email : mte@users.sourceforge.net,
+ ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "mymoneytransaction.h"
+
+MyMoneyTransaction::MyMoneyTransaction() :
+ MyMoneyObject()
+{
+ m_nextSplitID = 1;
+ m_entryDate = QDate();
+ m_postDate = QDate();
+}
+
+MyMoneyTransaction::MyMoneyTransaction(const QString id, const MyMoneyTransaction& transaction) :
+ MyMoneyObject(id)
+{
+ *this = transaction;
+ m_id = id;
+ if(m_entryDate == QDate())
+ m_entryDate = QDate::currentDate();
+
+ QValueList<MyMoneySplit>::Iterator it;
+ for(it = m_splits.begin(); it != m_splits.end(); ++it) {
+ (*it).setTransactionId(id);
+ }
+}
+
+MyMoneyTransaction::MyMoneyTransaction(const QDomElement& node, const bool forceId) :
+ MyMoneyObject(node, forceId)
+{
+ if("TRANSACTION" != node.tagName())
+ throw new MYMONEYEXCEPTION("Node was not TRANSACTION");
+
+ m_nextSplitID = 1;
+
+ m_postDate = stringToDate(node.attribute("postdate"));
+ m_entryDate = stringToDate(node.attribute("entrydate"));
+ m_bankID = QStringEmpty(node.attribute("bankid"));
+ m_memo = QStringEmpty(node.attribute("memo"));
+ m_commodity = QStringEmpty(node.attribute("commodity"));
+
+ QDomNode child = node.firstChild();
+ while ( !child.isNull() && child.isElement() )
+ {
+ QDomElement c = child.toElement();
+ if(c.tagName() == "SPLITS") {
+ // Process any split information found inside the transaction entry.
+ QDomNodeList nodeList = c.elementsByTagName("SPLIT");
+ for(unsigned int i = 0; i < nodeList.count(); ++i) {
+ MyMoneySplit s(nodeList.item(i).toElement());
+ if(!m_bankID.isEmpty())
+ s.setBankID(m_bankID);
+ if(!s.accountId().isEmpty())
+ addSplit(s);
+ else
+ qDebug("Dropped split because it did not have an account id");
+ }
+
+ } else if(c.tagName() == "KEYVALUEPAIRS") {
+ MyMoneyKeyValueContainer kvp(c);
+ setPairs(kvp.pairs());
+ }
+ child = child.nextSibling();
+ }
+ m_bankID = QString();
+}
+
+MyMoneyTransaction::~MyMoneyTransaction()
+{
+}
+
+bool MyMoneyTransaction::operator == (const MyMoneyTransaction& right) const
+{
+ return (MyMoneyObject::operator==(right) &&
+ MyMoneyKeyValueContainer::operator==(right) &&
+ (m_commodity == right.m_commodity) &&
+ ((m_memo.length() == 0 && right.m_memo.length() == 0) || (m_memo == right.m_memo)) &&
+ (m_splits == right.m_splits) &&
+ (m_entryDate == right.m_entryDate) &&
+ (m_postDate == right.m_postDate) );
+}
+
+bool MyMoneyTransaction::accountReferenced(const QString& id) const
+{
+ QValueList<MyMoneySplit>::ConstIterator it;
+
+ for(it = m_splits.begin(); it != m_splits.end(); ++it) {
+ if((*it).accountId() == id)
+ return true;
+ }
+ return false;
+}
+
+void MyMoneyTransaction::addSplit(MyMoneySplit& split)
+{
+ if(!split.id().isEmpty())
+ throw new MYMONEYEXCEPTION("Cannot add split with assigned id (" + split.id() + ")");
+
+/*
+ QValueList<MyMoneySplit>::Iterator it;
+
+ // if the account referenced in this split is already
+ // referenced in another split, we add the amount of
+ // this split to the other one. All other data contained
+ // in the new split will be discarded.
+ for(it = m_splits.begin(); it != m_splits.end(); ++it) {
+ if((*it).accountId() == split.accountId()) {
+ (*it).setValue((*it).value()+split.value());
+ split = (*it);
+ return;
+ }
+ }
+*/
+
+ if(split.accountId().isEmpty())
+ throw new MYMONEYEXCEPTION("Cannot add split that does not contain an account reference");
+
+ MyMoneySplit newSplit(nextSplitID(), split);
+ split = newSplit;
+ split.setTransactionId(id());
+ m_splits.append(split);
+}
+
+void MyMoneyTransaction::modifySplit(MyMoneySplit& split)
+{
+// This version of the routine allows only a single
+// split to reference one account. If a second split
+// is modified to reference an account already referenced
+// by another split, the values will be added and the
+// duplicate removed.
+/*
+ QValueList<MyMoneySplit>::Iterator it;
+ QValueList<MyMoneySplit>::Iterator self = m_splits.end();
+ QValueList<MyMoneySplit>::Iterator dup = self;
+ bool duplicateAccount = false;
+
+ for(it = m_splits.begin(); it != m_splits.end(); ++it) {
+ if(split.id() == (*it).id()) {
+ self = it;
+ } else if(split.accountId() == (*it).accountId()) {
+ (*it).setValue((*it).value() + split.value());
+ dup = it;
+ duplicateAccount = true;
+ }
+ }
+
+ if(self == m_splits.end())
+ throw new MYMONEYEXCEPTION("Invalid split id '" + split.id() + "'");
+
+ if(duplicateAccount) {
+ m_splits.remove(self);
+ split = *dup;
+ } else
+ *self = split;
+*/
+
+// This is the other version which allows having more splits referencing
+// the same account.
+ if(split.accountId().isEmpty())
+ throw new MYMONEYEXCEPTION("Cannot modify split that does not contain an account reference");
+
+ QValueList<MyMoneySplit>::Iterator it;
+ for(it = m_splits.begin(); it != m_splits.end(); ++it) {
+ if(split.id() == (*it).id()) {
+ *it = split;
+ return;
+ }
+ }
+ throw new MYMONEYEXCEPTION(QString("Invalid split id '%1'").arg(split.id()));
+}
+
+void MyMoneyTransaction::removeSplit(const MyMoneySplit& split)
+{
+ QValueList<MyMoneySplit>::Iterator it;
+
+ for(it = m_splits.begin(); it != m_splits.end(); ++it) {
+ if(split.id() == (*it).id()) {
+ m_splits.remove(it);
+ break;
+ }
+ }
+ if(it == m_splits.end())
+ throw new MYMONEYEXCEPTION(QString("Invalid split id '%1'").arg(split.id()));
+}
+
+void MyMoneyTransaction::removeSplits(void)
+{
+ m_splits.clear();
+ m_nextSplitID = 1;
+}
+
+const MyMoneySplit& MyMoneyTransaction::splitByPayee(const QString& payeeId) const
+{
+ QValueList<MyMoneySplit>::ConstIterator it;
+
+ for(it = m_splits.begin(); it != m_splits.end(); ++it) {
+ if((*it).payeeId() == payeeId)
+ return *it;
+ }
+ throw new MYMONEYEXCEPTION(QString("Split not found for payee '%1'").arg(QString(payeeId)));
+}
+
+const MyMoneySplit& MyMoneyTransaction::splitByAccount(const QString& accountId, const bool match) const
+{
+ QValueList<MyMoneySplit>::ConstIterator it;
+
+ for(it = m_splits.begin(); it != m_splits.end(); ++it) {
+ if(match == true && (*it).accountId() == accountId)
+ return *it;
+ if(match == false && (*it).accountId() != accountId)
+ return *it;
+ }
+ throw new MYMONEYEXCEPTION(QString("Split not found for account %1%2").arg(match?"":"!").arg(QString(accountId)));
+}
+
+const MyMoneySplit& MyMoneyTransaction::splitByAccount(const QStringList& accountIds, const bool match) const
+{
+ QValueList<MyMoneySplit>::ConstIterator it;
+
+ for(it = m_splits.begin(); it != m_splits.end(); ++it) {
+ if(match == true && accountIds.contains((*it).accountId()) )
+ return *it;
+ if(match == false && !accountIds.contains((*it).accountId()))
+ return *it;
+ }
+ throw new MYMONEYEXCEPTION(QString("Split not found for account %1%1...%2").arg(match?"":"!").arg(accountIds.front(),accountIds.back()));
+}
+
+const MyMoneySplit& MyMoneyTransaction::splitById(const QString& splitId) const
+{
+ QValueList<MyMoneySplit>::ConstIterator it;
+
+ for(it = m_splits.begin(); it != m_splits.end(); ++it) {
+ if((*it).id() == splitId)
+ return *it;
+ }
+ throw new MYMONEYEXCEPTION(QString("Split not found for id '%1'").arg(QString(splitId)));
+}
+
+const QString MyMoneyTransaction::nextSplitID()
+{
+ QString id;
+ id = "S" + id.setNum(m_nextSplitID++).rightJustify(SPLIT_ID_SIZE, '0');
+ return id;
+}
+
+const QString MyMoneyTransaction::firstSplitID()
+{
+ QString id;
+ id = "S" + id.setNum(1).rightJustify(SPLIT_ID_SIZE, '0');
+ return id;
+}
+
+const MyMoneyMoney MyMoneyTransaction::splitSum(void) const
+{
+ MyMoneyMoney result(0);
+ QValueList<MyMoneySplit>::ConstIterator it;
+
+ for(it = m_splits.begin(); it != m_splits.end(); ++it) {
+ result += (*it).value();
+ }
+ return result;
+}
+
+void MyMoneyTransaction::setPostDate(const QDate& date) { m_postDate = date; }
+void MyMoneyTransaction::setEntryDate(const QDate& date) { m_entryDate = date; }
+void MyMoneyTransaction::setMemo(const QString& memo) { m_memo = memo; }
+
+bool MyMoneyTransaction::isLoanPayment(void) const
+{
+ try {
+ QValueList<MyMoneySplit>::ConstIterator it;
+
+ for(it = m_splits.begin(); it != m_splits.end(); ++it) {
+ if((*it).isAmortizationSplit())
+ return true;
+ }
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+ return false;
+}
+
+const MyMoneySplit& MyMoneyTransaction::amortizationSplit(void) const
+{
+ static MyMoneySplit nullSplit;
+
+ QValueList<MyMoneySplit>::ConstIterator it;
+
+ for(it = m_splits.begin(); it != m_splits.end(); ++it) {
+ if((*it).isAmortizationSplit() && (*it).isAutoCalc())
+ return *it;
+ }
+ return nullSplit;
+}
+
+const MyMoneySplit& MyMoneyTransaction::interestSplit(void) const
+{
+ static MyMoneySplit nullSplit;
+
+ QValueList<MyMoneySplit>::ConstIterator it;
+
+ for(it = m_splits.begin(); it != m_splits.end(); ++it) {
+ if((*it).isInterestSplit() && (*it).isAutoCalc())
+ return *it;
+ }
+ return nullSplit;
+}
+
+unsigned long MyMoneyTransaction::hash(const QString& txt, unsigned long h)
+{
+ unsigned long g;
+
+ for(unsigned i=0; i < txt.length(); ++i) {
+ unsigned short uc = txt[i].unicode();
+ for(unsigned j = 0; j < 2; ++j) {
+ unsigned char c = uc & 0xff;
+ // if either the cell or the row of the Unicode char is 0, stop processing
+ if(!c)
+ break;
+ h = (h << 4) + c;
+ if( (g = (h & 0xf0000000)) ) {
+ h = h ^ (g >> 24);
+ h = h ^ g;
+ }
+ uc >>= 8;
+ }
+ }
+ return h;
+}
+
+bool MyMoneyTransaction::isStockSplit(void) const
+{
+ return (m_splits.count() == 1 && m_splits[0].action() == MyMoneySplit::ActionSplitShares);
+}
+
+bool MyMoneyTransaction::isImported(void) const
+{
+ return value("Imported").lower() == QString("true");
+}
+
+void MyMoneyTransaction::setImported(bool state)
+{
+ if(state)
+ setValue("Imported", "true");
+ else
+ deletePair("Imported");
+}
+
+bool MyMoneyTransaction::isDuplicate(const MyMoneyTransaction& r) const
+{
+ bool rc = true;
+ if(splitCount() != r.splitCount()) {
+ rc = false;
+ } else {
+ if(abs(m_postDate.daysTo(r.postDate())) > 3) {
+ rc = false;
+ } else {
+ unsigned long accHash[2];
+ unsigned long valHash[2];
+ unsigned long numHash[2];
+ for(int i = 0; i < 2; ++i)
+ accHash[i] = valHash[i] = numHash[i] = 0;
+
+ QValueList<MyMoneySplit>::ConstIterator it;
+ for(it = splits().begin(); it != splits().end(); ++it) {
+ accHash[0] += hash((*it).accountId());
+ valHash[0] += hash((*it).value().formatMoney("", 4));
+ numHash[0] += hash((*it).number());
+ }
+ for(it = r.splits().begin(); it != r.splits().end(); ++it) {
+ accHash[1] += hash((*it).accountId());
+ valHash[1] += hash((*it).value().formatMoney("", 4));
+ numHash[1] += hash((*it).number());
+ }
+
+ if(accHash[0] != accHash[1]
+ || valHash[0] != valHash[1]
+ || numHash[0] != numHash[1]
+ ) {
+ rc = false;
+ }
+ }
+ }
+
+ return rc;
+}
+
+void MyMoneyTransaction::writeXML(QDomDocument& document, QDomElement& parent) const
+{
+ QDomElement el = document.createElement("TRANSACTION");
+
+ writeBaseXML(document, el);
+
+ el.setAttribute("postdate", dateToString(m_postDate));
+ el.setAttribute("memo", m_memo);
+ el.setAttribute("entrydate", dateToString(m_entryDate));
+ el.setAttribute("commodity", m_commodity);
+
+ QDomElement splits = document.createElement("SPLITS");
+ QValueList<MyMoneySplit>::ConstIterator it;
+ for(it = m_splits.begin(); it != m_splits.end(); ++it) {
+ (*it).writeXML(document, splits);
+ }
+ el.appendChild(splits);
+
+ MyMoneyKeyValueContainer::writeXML(document, el);
+
+ parent.appendChild(el);
+}
+
+bool MyMoneyTransaction::hasReferenceTo(const QString& id) const
+{
+ QValueList<MyMoneySplit>::const_iterator it;
+ bool rc = (id == m_commodity);
+ for(it = m_splits.begin(); rc == false && it != m_splits.end(); ++it) {
+ rc = (*it).hasReferenceTo(id);
+ }
+ return rc;
+}
+
+bool MyMoneyTransaction::hasAutoCalcSplit(void) const
+{
+ QValueList<MyMoneySplit>::ConstIterator it;
+
+ for(it = m_splits.begin(); it != m_splits.end(); ++it) {
+ if((*it).isAutoCalc())
+ return true;
+ }
+ return false;
+}
+
+QString MyMoneyTransaction::accountSignature(bool includeSplitCount) const
+{
+ QMap<QString, int> accountList;
+ QValueList<MyMoneySplit>::const_iterator it_s;
+ for(it_s = m_splits.begin(); it_s != m_splits.end(); ++it_s) {
+ accountList[(*it_s).accountId()] += 1;
+ }
+
+ QMap<QString, int>::const_iterator it_a;
+ QString rc;
+ for(it_a = accountList.begin(); it_a != accountList.end(); ++it_a) {
+ if(it_a != accountList.begin())
+ rc += "-";
+ rc += it_a.key();
+ if(includeSplitCount)
+ rc += QString("*%1").arg(*it_a);
+ }
+ return rc;
+}
+
+QString MyMoneyTransaction::uniqueSortKey(void) const
+{
+ QString year, month, day, key;
+ const QDate& postdate = postDate();
+ year = year.setNum(postdate.year()).rightJustify(YEAR_SIZE, '0');
+ month = month.setNum(postdate.month()).rightJustify(MONTH_SIZE, '0');
+ day = day.setNum(postdate.day()).rightJustify(DAY_SIZE, '0');
+ key = year + "-" + month + "-" + day + "-" + m_id;
+ return key;
+}
diff --git a/kmymoney2/mymoney/mymoneytransaction.h b/kmymoney2/mymoney/mymoneytransaction.h
new file mode 100644
index 0000000..ce52779
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneytransaction.h
@@ -0,0 +1,353 @@
+/***************************************************************************
+ mymoneytransaction.h
+ -------------------
+ copyright : (C) 2000 by Michael Edwardes
+ (C) 2002 by Thomas Baumgart
+ email : mte@users.sourceforge.net
+ ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYTRANSACTION_H
+#define MYMONEYTRANSACTION_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstring.h>
+#include <qdatetime.h>
+#include <qptrlist.h>
+#include <qstringlist.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "mymoneyutils.h"
+#include "mymoneymoney.h"
+#include "mymoneykeyvaluecontainer.h"
+#include "mymoneysplit.h"
+#include <kmymoney/export.h>
+
+/**
+ * This class represents a transaction within the MyMoneyEngine. A transaction
+ * contains none, one or more splits of type MyMoneySplit. They are stored in
+ * a QValueList<MyMoneySplit> within this object. A transaction containing only
+ * a single split with an amount not equal to 0 is an unbalanced transaction. It
+ * is tolerated by the engine, but in general not a good idea as it is financially
+ * wrong.
+ */
+class KMYMONEY_EXPORT MyMoneyTransaction : public MyMoneyObject, public MyMoneyKeyValueContainer
+{
+public:
+ MyMoneyTransaction();
+ MyMoneyTransaction(const QString id,
+ const MyMoneyTransaction& transaction);
+ /**
+ * @param node reference to QDomNode
+ * @param forceId see MyMoneyObject(const QDomElement&, const bool)
+ */
+ MyMoneyTransaction(const QDomElement& node, const bool forceId = true);
+ ~MyMoneyTransaction();
+
+public:
+ friend QDataStream &operator<<(QDataStream &, MyMoneyTransaction &);
+ friend QDataStream &operator>>(QDataStream &, MyMoneyTransaction &);
+
+ // Simple get operations
+ const QDate& entryDate(void) const { return m_entryDate; };
+ const QDate& postDate(void) const { return m_postDate; };
+ const QString& memo(void) const { return m_memo; };
+ const QValueList<MyMoneySplit>& splits(void) const { return m_splits; };
+ QValueList<MyMoneySplit>& splits(void) { return m_splits; };
+ unsigned int splitCount(void) const { return m_splits.count(); };
+ const QString& commodity(void) const { return m_commodity; };
+ const QString& bankID(void) const /*__attribute__ ((deprecated))*/ { return m_bankID; };
+
+ // Simple set operations
+ void setPostDate(const QDate& date);
+ void setEntryDate(const QDate& date);
+ void setMemo(const QString& memo);
+ void setCommodity(const QString& commodityId) { m_commodity = commodityId; };
+ void setBankID(const QString& bankID) /*__attribute__ ((deprecated))*/ { m_bankID = bankID; };
+
+ bool operator == (const MyMoneyTransaction&) const;
+ inline bool operator != (const MyMoneyTransaction& r) const { return !(*this == r); };
+ bool operator< (const MyMoneyTransaction& r) const { return postDate() < r.postDate(); };
+ bool operator<= (const MyMoneyTransaction& r) const { return postDate() <= r.postDate(); };
+ bool operator> (const MyMoneyTransaction& r) const { return postDate() > r.postDate(); };
+
+ /**
+ * This method is used to extract a split for a given accountId
+ * from a transaction. A parameter controls, whether the accountId
+ * should match or not. In case of 'not match', the first not-matching
+ * split is returned.
+ *
+ * @param accountId the account to look for
+ * @param match if true, the account Id must match
+ * if false, the account Id must not match
+ *
+ * @return reference to split within the transaction is returned
+ */
+ const MyMoneySplit& splitByAccount(const QString& accountId, const bool match = true) const;
+
+ /**
+ * This method is essentially the same as the previous method, except that
+ * takes a list of accounts instead of just one.
+ *
+ * @param accountIds the list of accounts to look for
+ * @param match if true, the account Id must match
+ * if false, the account Id must not match
+ *
+ * @return reference to split within the transaction is returned
+ */
+ const MyMoneySplit& splitByAccount(const QStringList& accountIds, const bool match = true) const;
+
+ /**
+ * This method is used to extract a split from a transaction.
+ *
+ * @param splitId the split to look for
+ *
+ * @return reference to split within the transaction is returned
+ */
+ const MyMoneySplit& splitById(const QString& splitId) const;
+
+ /**
+ * This method is used to extract a split for a given payeeId
+ * from a transaction.
+ *
+ * @param payeeId the payee to look for
+ *
+ * @return reference to split within the transaction is returned
+ */
+ const MyMoneySplit& splitByPayee(const QString& payeeId) const;
+
+ /**
+ * This method is used to check if the given account is used
+ * in any of the splits of this transation
+ *
+ * @param id account id that should be checked for usage
+ */
+ bool accountReferenced(const QString& id) const;
+
+ /**
+ * This method is used to add a split to the transaction. The split
+ * will be assigned an id. The id member must be empty and the
+ * accountId member must be filled.
+ *
+ * @param split reference to the split that should be added
+ *
+ */
+ void addSplit(MyMoneySplit& split);
+
+ /**
+ * This method is used to modify a split in a transaction
+ */
+ void modifySplit(MyMoneySplit& split);
+
+ /**
+ * This method is used to remove a split from a transaction
+ */
+ void removeSplit(const MyMoneySplit& split);
+
+ /**
+ * This method is used to remove all splits from a transaction
+ */
+ void removeSplits(void);
+
+ /**
+ * This method is used to return the sum of all splits of this transaction
+ *
+ * @return MyMoneyMoney value of sum of all splits
+ */
+ const MyMoneyMoney splitSum(void) const;
+
+ /**
+ * This method returns information if the transaction
+ * contains information of a loan payment or not.
+ * Loan payment transactions have at least one
+ * split that is identified with a MyMoneySplit::action() of type
+ * MyMoneySplit::ActionAmortization.
+ *
+ * @retval false transaction is no loan payment transaction
+ * @retval true transaction is a loan payment transaction
+ *
+ * @note Upon internal failures, the return value @p false will be used.
+ */
+ bool isLoanPayment(void) const;
+
+ /**
+ * This method returns a const reference to the amortization split.
+ * In case none is found, a reference to an empty split will be returned.
+ */
+ const MyMoneySplit& amortizationSplit(void) const;
+
+ /**
+ * This method returns a const reference to the interest split.
+ * In case none is found, a reference to an empty split will be returned.
+ */
+ const MyMoneySplit& interestSplit(void) const;
+
+ /**
+ * This method is used to check if two transactions are identical.
+ * Identical transactions have:
+ *
+ * - the same number of splits
+ * - reference the same accounts
+ * - have the same values in the splits
+ * - have a postDate wihtin 3 days
+ *
+ * @param transaction reference to the transaction to be checked
+ * against this transaction
+ * @retval true transactions are identical
+ * @retval false transactions are not identical
+ */
+ bool isDuplicate(const MyMoneyTransaction& transaction) const;
+
+ /**
+ * returns @a true if this is a stock split transaction
+ */
+ bool isStockSplit(void) const;
+
+ /**
+ * returns @a true if this is an imported transaction
+ */
+ bool isImported(void) const;
+
+ /**
+ * Sets the imported state of this transaction to be the value of @a state .
+ * @p state defaults to @p true.
+ */
+ void setImported(bool state = true);
+
+ /**
+ * This static method returns the id which will be assigned to the
+ * first split added to a transaction. This ID can be used to figure
+ * out the split that references the account through which a transaction
+ * was entered.
+ *
+ * @return QString with ID of the first split of transactions
+ */
+ static const QString firstSplitID(void);
+
+ void writeXML(QDomDocument& document, QDomElement& parent) const;
+
+ /**
+ * This method checks if a reference to the given object exists. It returns,
+ * a @p true if the object is referencing the one requested by the
+ * parameter @p id. If it does not, this method returns @p false.
+ *
+ * @param id id of the object to be checked for references
+ * @retval true This object references object with id @p id.
+ * @retval false This object does not reference the object with id @p id.
+ */
+ virtual bool hasReferenceTo(const QString& id) const;
+
+ /**
+ * Checks whether any split contains an autocalc split.
+ *
+ * @retval true at least one split has an autocalc value
+ * @retval false all splits have fixed values
+ */
+ bool hasAutoCalcSplit(void) const;
+
+ /**
+ * Returns a signature consisting of the account ids and the
+ * number of times they occur in the transaction if @a includeSplitCount
+ * is @a true. The signature is independant from the order of splits.
+ *
+ * Example: Having splits referencing the account B, A and B, the returned
+ * value will be "A-B" if @p includeSplitCount is @p false or A*1-B*2 if it
+ * is @p true.
+ *
+ * The same result will be returned if the list of splits is A, B, B.
+ *
+ * @param includeSplitCount if @p true, the string @p *n with @p n being
+ * the number of splits referencing this account. The default for
+ * this parameter is @p false.
+ */
+ QString accountSignature(bool includeSplitCount = false) const;
+
+ QString uniqueSortKey(void) const;
+
+ /**
+ * This module implements an algorithm used by P.J. Weinberger
+ * for fast hashing. Source: COMPILERS by Alfred V. Aho,
+ * pages 435-437.
+ *
+ * It converts the string passed in @p txt into a non-unique
+ * unsigned long integer value.
+ *
+ * @param txt the text to be hashed
+ * @param h initial hash value (default 0)
+ * @return non-unique hash value of the text @p txt
+ */
+ static unsigned long hash(const QString& txt, unsigned long h = 0);
+
+private:
+ /**
+ * This method returns the next id to be used for a split
+ */
+ const QString nextSplitID(void);
+
+private:
+ static const int SPLIT_ID_SIZE = 4;
+
+ /**
+ * This member contains the date when the transaction was entered
+ * into the engine
+ */
+ QDate m_entryDate;
+
+ /**
+ * This member contains the date the transaction was posted
+ */
+ QDate m_postDate;
+
+ /**
+ * This member keeps the memo text associated with this transaction
+ */
+ QString m_memo;
+
+ /**
+ * This member contains the splits for this transaction
+ */
+ QValueList<MyMoneySplit> m_splits;
+
+ /**
+ * This member keeps the unique numbers of splits within this
+ * transaction. Upon creation of a MyMoneyTransaction object this
+ * value will be set to 1.
+ */
+ unsigned int m_nextSplitID;
+
+ /**
+ * This member keeps the base commodity (e.g. currency) for this transaction
+ */
+ QString m_commodity;
+
+ /**
+ * This member keeps the bank's unique ID for the transaction, so we can
+ * avoid duplicates. This is only used for electronic statement downloads.
+ *
+ * Note this is now deprecated! Bank ID's should be set on splits, not transactions.
+ */
+ QString m_bankID;
+
+ /** constants for unique sort key */
+ static const int YEAR_SIZE = 4;
+ static const int MONTH_SIZE = 2;
+ static const int DAY_SIZE = 2;
+};
+#endif
diff --git a/kmymoney2/mymoney/mymoneytransactionfilter.cpp b/kmymoney2/mymoney/mymoneytransactionfilter.cpp
new file mode 100644
index 0000000..317400d
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneytransactionfilter.cpp
@@ -0,0 +1,860 @@
+/***************************************************************************
+ mymoneytransactionfilter.cpp - description
+ -------------------
+ begin : Fri Aug 22 2003
+ copyright : (C) 2003 by Thomas Baumgart
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+// ----------------------------------------------------------------------------
+// KDE Includes
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include <kmymoney/mymoneyfile.h>
+#include "mymoneytransactionfilter.h"
+
+MyMoneyTransactionFilter::MyMoneyTransactionFilter()
+{
+ m_filterSet.allFilter = 0;
+ m_reportAllSplits = true;
+ m_considerCategory = true;
+ m_invertText = false;
+}
+
+MyMoneyTransactionFilter::MyMoneyTransactionFilter(const QString& id)
+{
+ m_filterSet.allFilter = 0;
+ m_reportAllSplits = false;
+ m_considerCategory = false;
+ m_invertText = false;
+
+ addAccount(id);
+ // addCategory(id);
+}
+
+MyMoneyTransactionFilter::~MyMoneyTransactionFilter()
+{
+}
+
+void MyMoneyTransactionFilter::clear(void)
+{
+ m_filterSet.allFilter = 0;
+ m_invertText = false;
+ m_accounts.clear();
+ m_categories.clear();
+ m_payees.clear();
+ m_types.clear();
+ m_states.clear();
+ m_validity.clear();
+ m_matchingSplits.clear();
+ m_fromDate = QDate();
+ m_toDate = QDate();
+}
+
+void MyMoneyTransactionFilter::clearAccountFilter(void)
+{
+ m_filterSet.singleFilter.accountFilter = 0;
+ m_accounts.clear();
+}
+
+void MyMoneyTransactionFilter::setTextFilter(const QRegExp& text, bool invert)
+{
+ m_filterSet.singleFilter.textFilter = 1;
+ m_invertText = invert;
+ m_text = text;
+}
+
+void MyMoneyTransactionFilter::addAccount(const QStringList& ids)
+{
+ QStringList::ConstIterator it;
+
+ m_filterSet.singleFilter.accountFilter = 1;
+ for(it = ids.begin(); it != ids.end(); ++it)
+ addAccount(*it);
+}
+
+void MyMoneyTransactionFilter::addAccount(const QString& id)
+{
+ if(!m_accounts.isEmpty() && !id.isEmpty()) {
+ if(m_accounts.find(id) != 0)
+ return;
+ }
+ if(m_accounts.count() >= m_accounts.size()*2) {
+ m_accounts.resize(457);
+ }
+ m_filterSet.singleFilter.accountFilter = 1;
+ if(!id.isEmpty())
+ m_accounts.insert(id, "");
+}
+
+void MyMoneyTransactionFilter::addCategory(const QStringList& ids)
+{
+ QStringList::ConstIterator it;
+
+ m_filterSet.singleFilter.categoryFilter = 1;
+ for(it = ids.begin(); it != ids.end(); ++it)
+ addCategory(*it);
+}
+
+void MyMoneyTransactionFilter::addCategory(const QString& id)
+{
+ if(!m_categories.isEmpty() && !id.isEmpty()) {
+ if(m_categories.find(id) != 0)
+ return;
+ }
+ if(m_categories.count() >= m_categories.size()*2) {
+ m_categories.resize(457);
+ }
+ m_filterSet.singleFilter.categoryFilter = 1;
+ if(!id.isEmpty())
+ m_categories.insert(id, "");
+}
+
+void MyMoneyTransactionFilter::setDateFilter(const QDate& from, const QDate& to)
+{
+ m_filterSet.singleFilter.dateFilter = from.isValid() | to.isValid();
+ m_fromDate = from;
+ m_toDate = to;
+}
+
+void MyMoneyTransactionFilter::setAmountFilter(const MyMoneyMoney& from, const MyMoneyMoney& to)
+{
+ m_filterSet.singleFilter.amountFilter = 1;
+ m_fromAmount = from.abs();
+ m_toAmount = to.abs();
+
+ // make sure that the user does not try to fool us ;-)
+ if(from > to) {
+ MyMoneyMoney tmp = m_fromAmount;
+ m_fromAmount = m_toAmount;
+ m_toAmount = tmp;
+ }
+}
+
+void MyMoneyTransactionFilter::addPayee(const QString& id)
+{
+ if(!m_payees.isEmpty() && !id.isEmpty()) {
+ if(m_payees.find(id) != 0)
+ return;
+ }
+ if(m_payees.count() >= m_payees.size()*2) {
+ m_payees.resize(457);
+ }
+ m_filterSet.singleFilter.payeeFilter = 1;
+ if(!id.isEmpty())
+ m_payees.insert(id, "");
+}
+
+void MyMoneyTransactionFilter::addType(const int type)
+{
+ if(!m_types.isEmpty()) {
+ if(m_types.find(type) != 0)
+ return;
+ }
+ // we don't have more than 4 or 5 types, so we don't worry about
+ // the size of the QIntDict object.
+ m_filterSet.singleFilter.typeFilter = 1;
+ m_types.insert(type, "");
+}
+
+void MyMoneyTransactionFilter::addState(const int state)
+{
+ if(!m_states.isEmpty()) {
+ if(m_states.find(state) != 0)
+ return;
+ }
+ // we don't have more than 4 or 5 states, so we don't worry about
+ // the size of the QIntDict object.
+ m_filterSet.singleFilter.stateFilter = 1;
+ m_states.insert(state, "");
+}
+
+void MyMoneyTransactionFilter::addValidity(const int type)
+{
+ if(!m_validity.isEmpty()) {
+ if(m_validity.find(type) != 0)
+ return;
+ }
+ // we don't have more than 4 or 5 states, so we don't worry about
+ // the size of the QIntDict object.
+ m_filterSet.singleFilter.validityFilter = 1;
+ m_validity.insert(type, "");
+}
+
+void MyMoneyTransactionFilter::setNumberFilter(const QString& from, const QString& to)
+{
+ m_filterSet.singleFilter.nrFilter = 1;
+ m_fromNr = from;
+ m_toNr = to;
+}
+
+void MyMoneyTransactionFilter::setReportAllSplits(const bool report)
+{
+ m_reportAllSplits = report;
+}
+
+void MyMoneyTransactionFilter::setConsiderCategory(const bool check)
+{
+ m_considerCategory = check;
+}
+
+const QValueList<MyMoneySplit>& MyMoneyTransactionFilter::matchingSplits(void) const
+{
+ return m_matchingSplits;
+}
+
+bool MyMoneyTransactionFilter::matchText(const MyMoneySplit * const sp) const
+{
+ // check if the text is contained in one of the fields
+ // memo, value, number, payee, account, date
+ if(m_filterSet.singleFilter.textFilter) {
+ MyMoneyFile* file = MyMoneyFile::instance();
+ const MyMoneyAccount& acc = file->account(sp->accountId());
+ const MyMoneySecurity& sec = file->security(acc.currencyId());
+ if(sp->memo().contains(m_text)
+ || sp->shares().formatMoney(acc.fraction(sec)).contains(m_text)
+ || sp->value().formatMoney(acc.fraction(sec)).contains(m_text)
+ || sp->number().contains(m_text))
+ return !m_invertText;
+
+ if(acc.name().contains(m_text))
+ return !m_invertText;
+
+ if(!sp->payeeId().isEmpty()) {
+ const MyMoneyPayee& payee = file->payee(sp->payeeId());
+ if(payee.name().contains(m_text))
+ return !m_invertText;
+ }
+ return m_invertText;
+ }
+ return true;
+}
+
+bool MyMoneyTransactionFilter::matchAmount(const MyMoneySplit * const sp) const
+{
+ if(m_filterSet.singleFilter.amountFilter) {
+ if(((sp->value().abs() < m_fromAmount) || sp->value().abs() > m_toAmount)
+ && ((sp->shares().abs() < m_fromAmount) || sp->shares().abs() > m_toAmount))
+ return false;
+ }
+
+ return true;
+}
+
+bool MyMoneyTransactionFilter::match(const MyMoneySplit * const sp) const
+{
+ return matchText(sp) && matchAmount(sp);
+}
+
+bool MyMoneyTransactionFilter::match(const MyMoneyTransaction& transaction)
+{
+ MyMoneyFile* file = MyMoneyFile::instance();
+ QValueList<MyMoneySplit>::ConstIterator it;
+
+ m_matchingSplits.clear();
+
+ // qDebug("T: %s", transaction.id().data());
+ // if no filter is set, we can savely return a match
+ // if we should report all splits, then we collect them
+ if(!m_filterSet.allFilter) {
+ if(m_reportAllSplits) {
+ m_matchingSplits = transaction.splits();
+ }
+ return true;
+ }
+
+ // perform checks on the MyMoneyTransaction object first
+
+ // check the date range
+ if(m_filterSet.singleFilter.dateFilter) {
+ if(m_fromDate != QDate()) {
+ if(transaction.postDate() < m_fromDate)
+ return false;
+ }
+
+ if(m_toDate != QDate()) {
+ if(transaction.postDate() > m_toDate)
+ return false;
+ }
+ }
+
+ // construct a local list of pointers to all splits and
+ // remove the ones that do not match account and/or categories.
+
+ QPtrList<MyMoneySplit> matchingSplits;
+ for(it = transaction.splits().begin(); it != transaction.splits().end(); ++it) {
+ matchingSplits.append(&(*it));
+ }
+
+ bool categoryMatched = !m_filterSet.singleFilter.categoryFilter;
+ bool accountMatched = !m_filterSet.singleFilter.accountFilter;
+ bool isTransfer = true;
+
+ // check the transaction's validity
+ if(m_filterSet.singleFilter.validityFilter) {
+ if(m_validity.count() > 0) {
+ if(!m_validity.find(validTransaction(transaction)))
+ return false;
+ }
+ }
+
+ MyMoneySplit* sp;
+
+ if(m_filterSet.singleFilter.accountFilter == 1
+ || m_filterSet.singleFilter.categoryFilter == 1) {
+ for(sp = matchingSplits.first(); sp != 0; ) {
+ MyMoneySplit* removeSplit = sp;
+ const MyMoneyAccount& acc = file->account(sp->accountId());
+ if(m_considerCategory) {
+ switch(acc.accountGroup()) {
+ case MyMoneyAccount::Income:
+ case MyMoneyAccount::Expense:
+ isTransfer = false;
+ // check if the split references one of the categories in the list
+ if(m_filterSet.singleFilter.categoryFilter) {
+ if(m_categories.count() > 0) {
+ if(m_categories.find(sp->accountId())) {
+ categoryMatched = true;
+ removeSplit = 0;
+ }
+ } else {
+ // we're looking for transactions with 'no' categories
+ return false;
+ }
+ }
+ break;
+
+ default:
+ // check if the split references one of the accounts in the list
+ if(m_filterSet.singleFilter.accountFilter) {
+ if(m_accounts.count() > 0) {
+ if(m_accounts.find(sp->accountId())) {
+ accountMatched = true;
+ removeSplit = 0;
+ }
+ }
+ } else
+ removeSplit = 0;
+
+ break;
+ }
+
+ } else {
+ if(m_filterSet.singleFilter.accountFilter) {
+ if(m_accounts.count() > 0) {
+ if(m_accounts.find(sp->accountId())) {
+ accountMatched = true;
+ removeSplit = 0;
+ }
+ }
+ } else
+ removeSplit = 0;
+ }
+
+ sp = matchingSplits.next();
+ if(removeSplit) {
+ // qDebug(" S: %s", (*it).id().data());
+ matchingSplits.remove(removeSplit);
+ }
+ }
+ }
+
+ // check if we're looking for transactions without assigned category
+ if(!categoryMatched && transaction.splitCount() == 1 && m_categories.count() == 0) {
+ categoryMatched = true;
+ }
+
+ // if there's no category filter and the category did not
+ // match, then we still want to see this transaction if it's
+ // a transfer
+ if(!categoryMatched && !m_filterSet.singleFilter.categoryFilter)
+ categoryMatched = isTransfer;
+
+ if(matchingSplits.count() == 0
+ || !(accountMatched && categoryMatched))
+ return false;
+
+ FilterSet filterSet = m_filterSet;
+ filterSet.singleFilter.dateFilter =
+ filterSet.singleFilter.accountFilter =
+ filterSet.singleFilter.categoryFilter = 0;
+
+ // check if we still have something to do
+ if(filterSet.allFilter != 0) {
+ for(sp = matchingSplits.first(); sp != 0;) {
+ MyMoneySplit* removeSplit = 0;
+ removeSplit = (matchAmount(sp) && matchText(sp)) ? 0 : sp;
+
+ const MyMoneyAccount& acc = file->account(sp->accountId());
+
+ // Determine if this account is a category or an account
+ bool isCategory = false;
+ switch(acc.accountGroup()) {
+ case MyMoneyAccount::Income:
+ case MyMoneyAccount::Expense:
+ isCategory = true;
+ default:
+ break;
+ }
+
+ if(!isCategory && !removeSplit) {
+ // check the payee list
+ if(!removeSplit && m_filterSet.singleFilter.payeeFilter) {
+ if(m_payees.count() > 0) {
+ if(sp->payeeId().isEmpty() || !m_payees.find(sp->payeeId()))
+ removeSplit = sp;
+ } else if(!sp->payeeId().isEmpty())
+ removeSplit = sp;
+ }
+
+ // check the type list
+ if(!removeSplit && m_filterSet.singleFilter.typeFilter) {
+ if(m_types.count() > 0) {
+ if(!m_types.find(splitType(transaction, *sp)))
+ removeSplit = sp;
+ }
+ }
+
+ // check the state list
+ if(!removeSplit && m_filterSet.singleFilter.stateFilter) {
+ if(m_states.count() > 0) {
+ if(!m_states.find(splitState(*sp)))
+ removeSplit = sp;
+ }
+ }
+
+ if(!removeSplit && m_filterSet.singleFilter.nrFilter) {
+ if(!m_fromNr.isEmpty()) {
+ if(sp->number() < m_fromNr)
+ removeSplit = sp;
+ }
+ if(!m_toNr.isEmpty()) {
+ if(sp->number() > m_toNr)
+ removeSplit = sp;
+ }
+ }
+ } else if(m_filterSet.singleFilter.payeeFilter
+ || m_filterSet.singleFilter.typeFilter
+ || m_filterSet.singleFilter.stateFilter
+ || m_filterSet.singleFilter.nrFilter)
+ removeSplit = sp;
+
+ sp = matchingSplits.next();
+ if(removeSplit) {
+ // qDebug(" S: %s", (*it).id().data());
+ matchingSplits.remove(removeSplit);
+ }
+ }
+ }
+
+ if(m_reportAllSplits == false && matchingSplits.count() != 0) {
+ m_matchingSplits.append(transaction.splits()[0]);
+ } else {
+ for(sp = matchingSplits.first(); sp != 0; sp = matchingSplits.next()) {
+ m_matchingSplits.append(*sp);
+ }
+ }
+ // all filters passed, I guess we have a match
+ // qDebug(" C: %d", m_matchingSplits.count());
+ return matchingSplits.count() != 0;
+}
+
+int MyMoneyTransactionFilter::splitState(const MyMoneySplit& split) const
+{
+ int rc = notReconciled;
+
+ switch(split.reconcileFlag()) {
+ default:
+ case MyMoneySplit::NotReconciled:
+ break;;
+
+ case MyMoneySplit::Cleared:
+ rc = cleared;
+ break;
+
+ case MyMoneySplit::Reconciled:
+ rc = reconciled;
+ break;
+
+ case MyMoneySplit::Frozen:
+ rc = frozen;
+ break;
+ }
+ return rc;
+}
+
+int MyMoneyTransactionFilter::splitType(const MyMoneyTransaction& t, const MyMoneySplit& split) const
+{
+ MyMoneyFile* file = MyMoneyFile::instance();
+ MyMoneyAccount a, b;
+ a = file->account(split.accountId());
+ if((a.accountGroup() == MyMoneyAccount::Income
+ || a.accountGroup() == MyMoneyAccount::Expense))
+ return allTypes;
+
+ if(t.splitCount() == 2) {
+ QString ida, idb;
+ ida = t.splits()[0].accountId();
+ idb = t.splits()[1].accountId();
+
+ a = file->account(ida);
+ b = file->account(idb);
+ if((a.accountGroup() != MyMoneyAccount::Expense
+ && a.accountGroup() != MyMoneyAccount::Income)
+ && (b.accountGroup() != MyMoneyAccount::Expense
+ && b.accountGroup() != MyMoneyAccount::Income))
+ return transfers;
+ }
+
+ if(split.value().isPositive())
+ return deposits;
+
+ return payments;
+}
+
+MyMoneyTransactionFilter::validityOptionE MyMoneyTransactionFilter::validTransaction(const MyMoneyTransaction& t) const
+{
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+ MyMoneyMoney val;
+
+ for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
+ val += (*it_s).value();
+ }
+ return (val == MyMoneyMoney(0,1)) ? valid : invalid;
+}
+
+bool MyMoneyTransactionFilter::includesCategory( const QString& cat ) const
+{
+ return (! m_filterSet.singleFilter.categoryFilter) || m_categories.find( cat );
+}
+
+bool MyMoneyTransactionFilter::includesAccount( const QString& acc ) const
+{
+ return (! m_filterSet.singleFilter.accountFilter) || m_accounts.find( acc );
+}
+
+bool MyMoneyTransactionFilter::includesPayee( const QString& pye ) const
+{
+ return (! m_filterSet.singleFilter.payeeFilter) || m_payees.find( pye );
+}
+
+bool MyMoneyTransactionFilter::dateFilter( QDate& from, QDate& to ) const
+{
+ from = m_fromDate;
+ to = m_toDate;
+ return m_filterSet.singleFilter.dateFilter==1;
+}
+
+bool MyMoneyTransactionFilter::amountFilter( MyMoneyMoney& from, MyMoneyMoney& to ) const
+{
+ from = m_fromAmount;
+ to = m_toAmount;
+ return m_filterSet.singleFilter.amountFilter==1;
+}
+
+bool MyMoneyTransactionFilter::numberFilter( QString& from, QString& to ) const
+{
+ from = m_fromNr;
+ to = m_toNr;
+ return m_filterSet.singleFilter.nrFilter==1;
+}
+
+bool MyMoneyTransactionFilter::payees(QStringList& list) const
+{
+ bool result = m_filterSet.singleFilter.payeeFilter;
+
+ if ( result )
+ {
+ QAsciiDictIterator<char> it_payee( m_payees );
+ while ( it_payee.current() )
+ {
+ list += it_payee.currentKey();
+ ++it_payee;
+ }
+ }
+ return result;
+}
+
+bool MyMoneyTransactionFilter::accounts(QStringList& list) const
+{
+ bool result = m_filterSet.singleFilter.accountFilter;
+
+ if ( result )
+ {
+ QAsciiDictIterator<char> it_account( m_accounts );
+ while ( it_account.current() )
+ {
+ QString account = it_account.currentKey();
+ list += account;
+ ++it_account;
+ }
+ }
+ return result;
+}
+
+bool MyMoneyTransactionFilter::categories(QStringList& list) const
+{
+ bool result = m_filterSet.singleFilter.categoryFilter;
+
+ if ( result )
+ {
+ QAsciiDictIterator<char> it_category( m_categories );
+ while ( it_category.current() )
+ {
+ list += it_category.currentKey();
+ ++it_category;
+ }
+ }
+ return result;
+}
+
+bool MyMoneyTransactionFilter::types(QValueList<int>& list) const
+{
+ bool result = m_filterSet.singleFilter.typeFilter;
+
+ if ( result )
+ {
+ QIntDictIterator<char> it_type( m_types );
+ while ( it_type.current() )
+ {
+ list += it_type.currentKey();
+ ++it_type;
+ }
+ }
+ return result;
+}
+
+bool MyMoneyTransactionFilter::states(QValueList<int>& list) const
+{
+ bool result = m_filterSet.singleFilter.stateFilter;
+
+ if ( result )
+ {
+ QIntDictIterator<char> it_state( m_states );
+ while ( it_state.current() )
+ {
+ list += it_state.currentKey();
+ ++it_state;
+ }
+ }
+ return result;
+}
+
+bool MyMoneyTransactionFilter::firstType(int&i) const
+{
+ bool result = m_filterSet.singleFilter.typeFilter;
+
+ if ( result )
+ {
+ QIntDictIterator<char> it_type( m_types );
+ if ( it_type.current() )
+ i = it_type.currentKey();
+ }
+ return result;
+}
+
+bool MyMoneyTransactionFilter::firstState(int&i) const
+{
+ bool result = m_filterSet.singleFilter.stateFilter;
+
+ if ( result )
+ {
+ QIntDictIterator<char> it_state( m_states );
+ if ( it_state.current() )
+ i = it_state.currentKey();
+ }
+ return result;
+}
+
+bool MyMoneyTransactionFilter::textFilter(QRegExp& exp) const
+{
+ exp = m_text;
+ return m_filterSet.singleFilter.textFilter == 1;
+}
+
+void MyMoneyTransactionFilter::setDateFilter(dateOptionE range)
+{
+ QDate from, to;
+ if ( translateDateRange(range,from,to) )
+ setDateFilter(from,to);
+}
+
+static int fiscalYearStartMonth = 1;
+static int fiscalYearStartDay = 1;
+
+void MyMoneyTransactionFilter::setFiscalYearStart(int firstMonth, int firstDay)
+{
+ fiscalYearStartMonth = firstMonth;
+ fiscalYearStartDay = firstDay;
+}
+
+bool MyMoneyTransactionFilter::translateDateRange(dateOptionE id, QDate& start, QDate& end)
+{
+ bool rc = true;
+ int yr, mon, day;
+ yr = QDate::currentDate().year();
+ mon = QDate::currentDate().month();
+ day = QDate::currentDate().day();
+ QDate tmp;
+
+ switch(id) {
+ case MyMoneyTransactionFilter::allDates:
+ start = QDate();
+ end = QDate();
+ break;
+ case MyMoneyTransactionFilter::asOfToday:
+ start = QDate();
+ end = QDate::currentDate();
+ break;
+ case MyMoneyTransactionFilter::currentMonth:
+ start = QDate(yr,mon,1);
+ end = QDate(yr,mon,1).addMonths(1).addDays(-1);
+ break;
+ case MyMoneyTransactionFilter::currentYear:
+ start = QDate(yr,1,1);
+ end = QDate(yr,12,31);
+ break;
+ case MyMoneyTransactionFilter::monthToDate:
+ start = QDate(yr,mon,1);
+ end = QDate::currentDate();
+ break;
+ case MyMoneyTransactionFilter::yearToDate:
+ start = QDate(yr,1,1);
+ end = QDate::currentDate();
+ break;
+ case MyMoneyTransactionFilter::yearToMonth:
+ start = QDate(yr,1,1);
+ end = QDate(yr,mon,1).addDays(-1);
+ break;
+ case MyMoneyTransactionFilter::lastMonth:
+ start = QDate(yr,mon,1).addMonths(-1);
+ end = QDate(yr,mon,1).addDays(-1);
+ break;
+ case MyMoneyTransactionFilter::lastYear:
+ start = QDate(yr,1,1).addYears(-1);
+ end = QDate(yr,12,31).addYears(-1);
+ break;
+ case MyMoneyTransactionFilter::last7Days:
+ start = QDate::currentDate().addDays(-7);
+ end = QDate::currentDate();
+ break;
+ case MyMoneyTransactionFilter::last30Days:
+ start = QDate::currentDate().addDays(-30);
+ end = QDate::currentDate();
+ break;
+ case MyMoneyTransactionFilter::last3Months:
+ start = QDate::currentDate().addMonths(-3);
+ end = QDate::currentDate();
+ break;
+ case MyMoneyTransactionFilter::last6Months:
+ start = QDate::currentDate().addMonths(-6);
+ end = QDate::currentDate();
+ break;
+ case MyMoneyTransactionFilter::last11Months:
+ start = QDate(yr,mon,1).addMonths(-12);
+ end = QDate(yr,mon,1).addDays(-1);
+ break;
+ case MyMoneyTransactionFilter::last12Months:
+ start = QDate::currentDate().addMonths(-12);
+ end = QDate::currentDate();
+ break;
+ case MyMoneyTransactionFilter::next7Days:
+ start = QDate::currentDate();
+ end = QDate::currentDate().addDays(7);
+ break;
+ case MyMoneyTransactionFilter::next30Days:
+ start = QDate::currentDate();
+ end = QDate::currentDate().addDays(30);
+ break;
+ case MyMoneyTransactionFilter::next3Months:
+ start = QDate::currentDate();
+ end = QDate::currentDate().addMonths(3);
+ break;
+ case MyMoneyTransactionFilter::next6Months:
+ start = QDate::currentDate();
+ end = QDate::currentDate().addMonths(6);
+ break;
+ case MyMoneyTransactionFilter::next12Months:
+ start = QDate::currentDate();
+ end = QDate::currentDate().addMonths(12);
+ break;
+ case MyMoneyTransactionFilter::userDefined:
+ start = QDate();
+ end = QDate();
+ break;
+ case MyMoneyTransactionFilter::last3ToNext3Months:
+ start = QDate::currentDate().addMonths(-3);
+ end = QDate::currentDate().addMonths(3);
+ break;
+ case MyMoneyTransactionFilter::currentQuarter:
+ start = QDate(yr, mon - ((mon-1) % 3), 1);
+ end = start.addMonths(3).addDays(-1);
+ break;
+ case MyMoneyTransactionFilter::lastQuarter:
+ start = QDate(yr, mon - ((mon-1) % 3), 1).addMonths(-3);
+ end = start.addMonths(3).addDays(-1);
+ break;
+ case MyMoneyTransactionFilter::nextQuarter:
+ start = QDate(yr, mon - ((mon-1) % 3), 1).addMonths(3);
+ end = start.addMonths(3).addDays(-1);
+ break;
+ case MyMoneyTransactionFilter::currentFiscalYear:
+ start = QDate(QDate::currentDate().year(), fiscalYearStartMonth, fiscalYearStartDay);
+ if(QDate::currentDate() < start)
+ start = start.addYears(-1);
+ end = start.addYears(1).addDays(-1);
+ break;
+ case MyMoneyTransactionFilter::lastFiscalYear:
+ start = QDate(QDate::currentDate().year(), fiscalYearStartMonth, fiscalYearStartDay);
+ if(QDate::currentDate() < start)
+ start = start.addYears(-1);
+ start = start.addYears(-1);
+ end = start.addYears(1).addDays(-1);
+ break;
+ case MyMoneyTransactionFilter::today:
+ start = QDate::currentDate();
+ end = QDate::currentDate();
+ break;
+ default:
+ qFatal("Unknown date identifier %d in MyMoneyTransactionFilter::translateDateRange()", id);
+ rc = false;
+ break;
+ }
+ return rc;
+}
+
+void MyMoneyTransactionFilter::removeReference(const QString& id)
+{
+ if(m_accounts.find(id)) {
+ qDebug("%s", (QString("Remove account '%1' from report").arg(id)).data());
+ m_accounts.remove(id);
+ } else if(m_categories.find(id)) {
+ qDebug("%s", (QString("Remove category '%1' from report").arg(id)).data());
+ m_categories.remove(id);
+ } else if(m_payees.find(id)) {
+ qDebug("%s", (QString("Remove payee '%1' from report").arg(id)).data());
+ m_payees.remove(id);
+ }
+}
+
+
+// vim:cin:si:ai:et:ts=2:sw=2:
diff --git a/kmymoney2/mymoney/mymoneytransactionfilter.h b/kmymoney2/mymoney/mymoneytransactionfilter.h
new file mode 100644
index 0000000..edad9cc
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneytransactionfilter.h
@@ -0,0 +1,578 @@
+/***************************************************************************
+ mymoneytransactionfilter.h - description
+ -------------------
+ begin : Fri Aug 22 2003
+ copyright : (C) 2000-2003 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYTRANSACTIONFILTER_H
+#define MYMONEYTRANSACTIONFILTER_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstring.h>
+#include <qdatetime.h>
+#include <qmap.h>
+#include <qasciidict.h>
+#include <qintdict.h>
+#include <qregexp.h>
+
+// ----------------------------------------------------------------------------
+// KDE Includes
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include <kmymoney/mymoneytransaction.h>
+#include <kmymoney/export.h>
+
+/**
+ * @author Thomas Baumgart
+ */
+
+class KMYMONEY_EXPORT MyMoneyTransactionFilter
+{
+public:
+ // Make sure to keep the following enum valus in sync with the values
+ // used by the GUI (for KMyMoney in kfindtransactiondlgdecl.ui)
+ enum typeOptionE {
+ allTypes = 0,
+ payments,
+ deposits,
+ transfers,
+ // insert new constants above of this line
+ typeOptionCount
+ };
+
+ // Make sure to keep the following enum valus in sync with the values
+ // used by the GUI (for KMyMoney in kfindtransactiondlgdecl.ui)
+ enum stateOptionE {
+ allStates = 0,
+ notReconciled,
+ cleared,
+ reconciled,
+ frozen,
+ // insert new constants above of this line
+ stateOptionCount
+ };
+
+ // Make sure to keep the following enum valus in sync with the values
+ // used by the GUI (for KMyMoney in kfindtransactiondlgdecl.ui)
+ enum validityOptionE {
+ anyValidity = 0,
+ valid,
+ invalid,
+ // insert new constants above of this line
+ validityOptionCount
+ };
+
+ // Make sure to keep the following enum valus in sync with the values
+ // used by the GUI (for KMyMoney in kfindtransactiondlgdecl.ui)
+ enum dateOptionE {
+ allDates = 0,
+ asOfToday,
+ currentMonth,
+ currentYear,
+ monthToDate,
+ yearToDate,
+ yearToMonth,
+ lastMonth,
+ lastYear,
+ last7Days,
+ last30Days,
+ last3Months,
+ last6Months,
+ last12Months,
+ next7Days,
+ next30Days,
+ next3Months,
+ next6Months,
+ next12Months,
+ userDefined,
+ last3ToNext3Months,
+ last11Months,
+ currentQuarter,
+ lastQuarter,
+ nextQuarter,
+ currentFiscalYear,
+ lastFiscalYear,
+ today,
+ // insert new constants above of this line
+ dateOptionCount
+ };
+
+ typedef union {
+ unsigned allFilter;
+ struct {
+ unsigned textFilter : 1;
+ unsigned accountFilter : 1;
+ unsigned payeeFilter : 1;
+ unsigned categoryFilter : 1;
+ unsigned nrFilter : 1;
+ unsigned dateFilter : 1;
+ unsigned amountFilter : 1;
+ unsigned typeFilter : 1;
+ unsigned stateFilter : 1;
+ unsigned validityFilter : 1;
+ } singleFilter;
+ } FilterSet;
+
+ /**
+ * This is the standard constructor for a transaction filter.
+ * It creates the object and calls setReportAllSplits() to
+ * report all matching splits as separate entries. Use
+ * setReportAllSplits() to override this behaviour.
+ */
+ MyMoneyTransactionFilter();
+
+ /**
+ * This is a convenience constructor to allow construction of
+ * a simple account filter. It is basically the same as the
+ * following:
+ *
+ * @code
+ * :
+ * MyMoneyTransactionFilter filter;
+ * filter.setReportAllSplits(false);
+ * filter.addAccount(id);
+ * :
+ * @endcode
+ *
+ * @param id reference to account id
+ */
+ MyMoneyTransactionFilter(const QString& id);
+
+ ~MyMoneyTransactionFilter();
+
+ /**
+ * This method is used to clear the filter. All settings will be
+ * removed.
+ */
+ void clear(void);
+
+ /**
+ * This method is used to clear the accounts filter only.
+ */
+ void clearAccountFilter(void);
+
+ /**
+ * This method is used to set the regular expression filter to the value specified
+ * as parameter @p exp. The following text based fields are searched:
+ *
+ * - Memo
+ * - Payee
+ * - Category
+ * - Shares / Value
+ * - Number
+ *
+ * @param exp The regular expression that must be found in a transaction
+ * before it is included in the result set.
+ * @param invert If true, value must not be contained in any of the above mentioned fields
+ *
+ */
+ void setTextFilter(const QRegExp& exp, bool invert = false);
+
+ /**
+ * This method will add the account with id @p id to the list of matching accounts.
+ * If the list is empty, any transaction will match.
+ *
+ * @param id internal ID of the account
+ */
+ void addAccount(const QString& id);
+
+ /**
+ * This is a convenience method and behaves exactly like the above
+ * method but for a list of id's.
+ */
+ void addAccount(const QStringList& ids);
+
+ /**
+ * This method will add the category with id @p id to the list of matching categories.
+ * If the list is empty, only transaction with a single asset/liability account will match.
+ *
+ * @param id internal ID of the account
+ */
+ void addCategory(const QString& id);
+
+ /**
+ * This is a convenience method and behaves exactly like the above
+ * method but for a list of id's.
+ */
+ void addCategory(const QStringList& ids);
+
+ /**
+ * This method sets the date filter to match only transactions with posting dates in
+ * the date range specified by @p from and @p to. If @p from equal QDate()
+ * all transactions with dates prior to @p to match. If @p to equals QDate()
+ * all transactions with posting dates past @p from match. If @p from and @p to
+ * are equal QDate() the filter is not activated and all transactions match.
+ *
+ * @param from from date
+ * @param to to date
+ */
+ void setDateFilter(const QDate& from, const QDate& to);
+
+ void setDateFilter(dateOptionE range);
+
+ /**
+ * This method sets the amount filter to match only transactions with
+ * an amount in the range specified by @p from and @p to.
+ * If a specific amount should be searched, @p from and @p to should be
+ * the same value.
+ *
+ * @param from smallest value to match
+ * @param to largest value to match
+ */
+ void setAmountFilter(const MyMoneyMoney& from, const MyMoneyMoney& to);
+
+ /**
+ * This method will add the payee with id @p id to the list of matching payees.
+ * If the list is empty, any transaction will match.
+ *
+ * @param id internal id of the payee
+ */
+ void addPayee(const QString& id);
+
+ /**
+ */
+ void addType(const int type);
+
+ /**
+ */
+ void addValidity(const int type);
+
+ /**
+ */
+ void addState(const int state);
+
+ /**
+ * This method sets the number filter to match only transactions with
+ * a number in the range specified by @p from and @p to.
+ * If a specific number should be searched, @p from and @p to should be
+ * the same value.
+ *
+ * @param from smallest value to match
+ * @param to largest value to match
+ *
+ * @note @p from and @p to can contain alphanumeric text
+ */
+ void setNumberFilter(const QString& from, const QString& to);
+
+ /**
+ * This method is used to check a specific transaction against the filter.
+ * The transaction will match the whole filter, if all specified filters
+ * match. If the filter is cleared using the clear() method, any transaciton
+ * matches.
+ *
+ * @param transaction A transaction
+ *
+ * @retval true The transaction matches the filter set
+ * @retval false The transaction does not match at least one of
+ * the filters in the filter set
+ */
+ bool match(const MyMoneyTransaction& transaction);
+
+ /**
+ * This method is used to check a specific split against the
+ * text filter. The split will match if all specified and
+ * checked filters match. If the filter is cleared using the clear()
+ * method, any split matches.
+ *
+ * @param sp pointer to the split to be checked
+ *
+ * @retval true The split matches the filter set
+ * @retval false The split does not match at least one of
+ * the filters in the filter set
+ */
+ bool matchText(const MyMoneySplit * const sp) const;
+
+ /**
+ * This method is used to check a specific split against the
+ * amount filter. The split will match if all specified and
+ * checked filters match. If the filter is cleared using the clear()
+ * method, any split matches.
+ *
+ * @param sp pointer to the split to be checked
+ *
+ * @retval true The split matches the filter set
+ * @retval false The split does not match at least one of
+ * the filters in the filter set
+ */
+ bool matchAmount(const MyMoneySplit * const sp) const;
+
+ /**
+ * Convenience method which actually returns matchText(sp) && matchAmount(sp).
+ */
+ bool match(const MyMoneySplit * const sp) const;
+
+ /**
+ * This method is used to switch the amount of splits reported
+ * by matchingSplits(). If the argument @p report is @p true (the default
+ * if no argument specified) then matchingSplits() will return all
+ * matching splits of the transaction. If @p report is set to @p false,
+ * then only the very first matching split will be returned by
+ * matchingSplits().
+ *
+ * @param report controls the behaviour of matchingsSplits() as explained above.
+ */
+ void setReportAllSplits(const bool report = true);
+
+ void setConsiderCategory(const bool check = true);
+
+ /**
+ * This method returns a list of the matching splits for the filter.
+ * If m_reportAllSplits is set to false, then only the very first
+ * split will be returned. Use setReportAllSplits() to change the
+ * behaviour.
+ *
+ * @return reference list of MyMoneySplit objects containing the
+ * matching splits. If multiple splits match, only the first
+ * one will be returned.
+ *
+ * @note an empty list will be returned, if the filter only required
+ * to check the data contained in the MyMoneyTransaction
+ * object (e.g. posting-date, state, etc.).
+ *
+ * @note The constructors set m_reportAllSplits differently. Please
+ * see the documentation of the constructors MyMoneyTransactionFilter()
+ * and MyMoneyTransactionFilter(const QString&) for details.
+ */
+ const QValueList<MyMoneySplit>& matchingSplits(void) const;
+
+ /**
+ * This method returns the from date set in the filter. If
+ * no value has been set up for this filter, then QDate() is
+ * returned.
+ *
+ * @return returns m_fromDate
+ */
+ const QDate fromDate(void) const { return m_fromDate; };
+
+ /**
+ * This method returns the to date set in the filter. If
+ * no value has been set up for this filter, then QDate() is
+ * returned.
+ *
+ * @return returns m_toDate
+ */
+ const QDate toDate(void) const { return m_toDate; };
+
+ /**
+ * This method is used to return information about the
+ * presence of a specific category in the category filter.
+ * The category in question is included in the filter set,
+ * if it has been set or no category filter is set.
+ *
+ * @param cat id of category in question
+ * @return true if category is in filter set, false otherwise
+ */
+ bool includesCategory( const QString& cat ) const;
+
+ /**
+ * This method is used to return information about the
+ * presence of a specific account in the account filter.
+ * The account in question is included in the filter set,
+ * if it has been set or no account filter is set.
+ *
+ * @param acc id of account in question
+ * @return true if account is in filter set, false otherwise
+ */
+ bool includesAccount( const QString& acc ) const;
+
+ /**
+ * This method is used to return information about the
+ * presence of a specific payee in the account filter.
+ * The payee in question is included in the filter set,
+ * if it has been set or no account filter is set.
+ *
+ * @param pye id of payee in question
+ * @return true if payee is in filter set, false otherwise
+ */
+ bool includesPayee( const QString& pye ) const;
+
+ /**
+ * This method is used to return information about the
+ * presence of a date filter.
+ *
+ * @param from result value for the beginning of the date range
+ * @param to result value for the end of the date range
+ * @return true if an amount filter is set
+ */
+ bool dateFilter( QDate& from, QDate& to ) const;
+
+ /**
+ * This method is used to return information about the
+ * presence of an amount filter.
+ *
+ * @param from result value for the low end of the amount range
+ * @param to result value for the high end of the amount range
+ * @return true if an amount filter is set
+ */
+ bool amountFilter( MyMoneyMoney& from, MyMoneyMoney& to ) const;
+
+ /**
+ * This method is used to return information about the
+ * presence of an number filter.
+ *
+ * @param from result value for the low end of the number range
+ * @param to result value for the high end of the number range
+ * @return true if a number filter is set
+ */
+ bool numberFilter( QString& from, QString& to ) const;
+
+ /**
+ * This method returns whether a payee filter has been set,
+ * and if so, it returns all the payees set in the filter.
+ *
+ * @param list list to append payees into
+ * @return return true if a payee filter has been set
+ */
+ bool payees(QStringList& list) const;
+
+ /**
+ * This method returns whether an account filter has been set,
+ * and if so, it returns all the accounts set in the filter.
+ *
+ * @param list list to append accounts into
+ * @return return true if an account filter has been set
+ */
+ bool accounts(QStringList& list) const;
+
+ /**
+ * This method returns whether a category filter has been set,
+ * and if so, it returns all the categories set in the filter.
+ *
+ * @param list list to append categories into
+ * @return return true if a category filter has been set
+ */
+ bool categories(QStringList& list) const;
+
+ /**
+ * This method returns whether a type filter has been set,
+ * and if so, it returns the first type in the filter.
+ *
+ * @param i int to replace with first type filter, untouched otherwise
+ * @return return true if a type filter has been set
+ */
+ bool firstType(int& i) const;
+
+ bool types(QValueList<int>& list) const;
+
+ /**
+ * This method returns whether a state filter has been set,
+ * and if so, it returns the first state in the filter.
+ *
+ * @param i reference to int to replace with first state filter, untouched otherwise
+ * @return return true if a state filter has been set
+ */
+ bool firstState(int& i) const;
+
+ bool states(QValueList<int>& list) const;
+ /**
+ * This method returns whether a text filter has been set,
+ * and if so, it returns the text filter.
+ *
+ * @param text regexp to replace with text filter, or blank if none set
+ * @return return true if a text filter has been set
+ */
+ bool textFilter(QRegExp& text) const;
+
+ /**
+ * This method returns whether the text filter should return
+ * that DO NOT contain the text
+ */
+ bool isInvertingText(void) const {return m_invertText;};
+
+ /**
+ * This method translates a plain-language date range into QDate
+ * start & end
+ *
+ * @param range Plain-language range of dates, e.g. 'CurrentYear'
+ * @param start QDate will be set to corresponding to the first date in @p range
+ * @param end QDate will be set to corresponding to the last date in @p range
+ * @return return true if a range was successfully set, or false if @p range was invalid
+ */
+ static bool translateDateRange(dateOptionE range, QDate& start, QDate& end);
+
+ static void setFiscalYearStart(int firstMonth, int firstDay);
+
+ FilterSet filterSet(void) const { return m_filterSet; };
+
+ /**
+ * This member removes all references to object identified by @p id. Used
+ * to remove objects which are about to be removed from the engine.
+ */
+ void removeReference(const QString& id);
+
+private:
+ /**
+ * This is a conversion tool from MyMoneySplit::reconcileFlagE
+ * to MyMoneyTransactionFilter::stateE types
+ *
+ * @param split reference to split in question
+ *
+ * @return converted reconcile flag of the split passed as parameter
+ */
+ int splitState(const MyMoneySplit& split) const;
+
+ /**
+ * This is a conversion tool from MyMoneySplit::action
+ * to MyMoneyTransactionFilter::typeE types
+ *
+ * @param t reference to transaction
+ * @param split reference to split in question
+ *
+ * @return converted action of the split passed as parameter
+ */
+ int splitType(const MyMoneyTransaction& t, const MyMoneySplit& split) const;
+
+ /**
+ * This method checks if a transaction is valid or not. A transaction
+ * is considered valid, if the sum of all splits is zero, invalid otherwise.
+ *
+ * @param transaction reference to transaction to be checked
+ * @retval valid transaction is valid
+ * @retval invalid transaction is invalid
+ */
+ validityOptionE validTransaction(const MyMoneyTransaction& transaction) const;
+
+protected:
+ FilterSet m_filterSet;
+ bool m_reportAllSplits;
+ bool m_considerCategory;
+
+ QRegExp m_text;
+ bool m_invertText;
+ QAsciiDict<char> m_accounts;
+ QAsciiDict<char> m_payees;
+ QAsciiDict<char> m_categories;
+ QIntDict<char> m_states;
+ QIntDict<char> m_types;
+ QIntDict<char> m_validity;
+ QString m_fromNr, m_toNr;
+ QDate m_fromDate, m_toDate;
+ MyMoneyMoney m_fromAmount, m_toAmount;
+ QValueList<MyMoneySplit> m_matchingSplits;
+};
+
+#endif
diff --git a/kmymoney2/mymoney/mymoneytransactiontest.cpp b/kmymoney2/mymoney/mymoneytransactiontest.cpp
new file mode 100644
index 0000000..11af6b3
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneytransactiontest.cpp
@@ -0,0 +1,628 @@
+/***************************************************************************
+ mymoneytransactiontest.cpp
+ -------------------
+ copyright : (C) 2002 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "mymoneytransactiontest.h"
+
+MyMoneyTransactionTest::MyMoneyTransactionTest ()
+{
+}
+
+
+void MyMoneyTransactionTest::setUp () {
+ m = new MyMoneyTransaction();
+}
+
+void MyMoneyTransactionTest::tearDown () {
+ delete m;
+}
+
+void MyMoneyTransactionTest::testEmptyConstructor() {
+ CPPUNIT_ASSERT(m->id().isEmpty());
+ CPPUNIT_ASSERT(m->entryDate() == QDate());
+ CPPUNIT_ASSERT(m->memo().isEmpty());
+ CPPUNIT_ASSERT(m->splits().count() == 0);
+}
+
+void MyMoneyTransactionTest::testSetFunctions() {
+ m->setMemo("Memo");
+ m->setPostDate(QDate(1,2,3));
+
+ CPPUNIT_ASSERT(m->postDate() == QDate(1,2,3));
+ CPPUNIT_ASSERT(m->memo() == "Memo");
+}
+
+void MyMoneyTransactionTest::testConstructor() {
+ testSetFunctions();
+ MyMoneyTransaction a("ID", *m);
+
+ CPPUNIT_ASSERT(a.id() == "ID");
+ CPPUNIT_ASSERT(a.entryDate() == QDate::currentDate());
+ CPPUNIT_ASSERT(a.memo() == "Memo");
+ CPPUNIT_ASSERT(a.postDate() == QDate(1,2,3));
+}
+
+void MyMoneyTransactionTest::testCopyConstructor() {
+ testConstructor();
+ MyMoneyTransaction a("ID", *m);
+ a.setValue("Key", "Value");
+
+ MyMoneyTransaction n(a);
+
+ CPPUNIT_ASSERT(n.id() == "ID");
+ CPPUNIT_ASSERT(n.entryDate() == QDate::currentDate());
+ CPPUNIT_ASSERT(n.memo() == "Memo");
+ CPPUNIT_ASSERT(n.postDate() == QDate(1,2,3));
+ CPPUNIT_ASSERT(n.value("Key") == "Value");
+}
+
+void MyMoneyTransactionTest::testAssignmentConstructor() {
+ testConstructor();
+ MyMoneyTransaction a("ID", *m);
+ a.setValue("Key", "Value");
+
+ MyMoneyTransaction n;
+
+ n = a;
+
+ CPPUNIT_ASSERT(n.id() == "ID");
+ CPPUNIT_ASSERT(n.entryDate() == QDate::currentDate());
+ CPPUNIT_ASSERT(n.memo() == "Memo");
+ CPPUNIT_ASSERT(n.postDate() == QDate(1,2,3));
+ CPPUNIT_ASSERT(n.value("Key") == "Value");
+}
+
+void MyMoneyTransactionTest::testEquality() {
+ testConstructor();
+
+ MyMoneyTransaction n(*m);
+
+ CPPUNIT_ASSERT(n == *m);
+ CPPUNIT_ASSERT(!(n != *m));
+}
+
+void MyMoneyTransactionTest::testInequality() {
+ testConstructor();
+
+ MyMoneyTransaction n(*m);
+
+ n.setPostDate(QDate(1,1,1));
+ CPPUNIT_ASSERT(!(n == *m));
+ CPPUNIT_ASSERT(n != *m);
+
+ n = *m;
+ n.setValue("key", "value");
+ CPPUNIT_ASSERT(!(n == *m));
+ CPPUNIT_ASSERT(n != *m);
+}
+
+void MyMoneyTransactionTest::testAddSplits() {
+ m->setId("TestID");
+ MyMoneySplit split1, split2;
+ split1.setAccountId("A000001");
+ split2.setAccountId("A000002");
+ split1.setValue(100);
+ split2.setValue(200);
+
+ try {
+ CPPUNIT_ASSERT(m->accountReferenced("A000001") == false);
+ CPPUNIT_ASSERT(m->accountReferenced("A000002") == false);
+ m->addSplit(split1);
+ m->addSplit(split2);
+ CPPUNIT_ASSERT(m->splitCount() == 2);
+ CPPUNIT_ASSERT(m->splits()[0].accountId() == "A000001");
+ CPPUNIT_ASSERT(m->splits()[1].accountId() == "A000002");
+ CPPUNIT_ASSERT(m->accountReferenced("A000001") == true);
+ CPPUNIT_ASSERT(m->accountReferenced("A000002") == true);
+ CPPUNIT_ASSERT(m->splits()[0].id() == "S0001");
+ CPPUNIT_ASSERT(m->splits()[1].id() == "S0002");
+ CPPUNIT_ASSERT(split1.id() == "S0001");
+ CPPUNIT_ASSERT(split2.id() == "S0002");
+ CPPUNIT_ASSERT(m->splits()[0].transactionId() == "TestID");
+ CPPUNIT_ASSERT(m->splits()[1].transactionId() == "TestID");
+ CPPUNIT_ASSERT(split1.transactionId() == "TestID");
+ CPPUNIT_ASSERT(split2.transactionId() == "TestID");
+
+ } catch(MyMoneyException *e) {
+ unexpectedException(e);
+ }
+
+ // try to add split with assigned ID
+ try {
+ m->addSplit(split1);
+ CPPUNIT_FAIL("Exception expected!");
+
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+}
+
+void MyMoneyTransactionTest::testModifySplits() {
+ testAddSplits();
+ MyMoneySplit split;
+
+ split = m->splits()[0];
+ split.setAccountId("A000003");
+ split.setId("S00000000");
+
+ // this one should fail, because the ID is invalid
+ try {
+ m->modifySplit(split);
+ CPPUNIT_FAIL("Exception expected");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+
+ // set id to correct value, and check that it worked
+ split.setId("S0001");
+ try {
+ m->modifySplit(split);
+ CPPUNIT_ASSERT(m->splitCount() == 2);
+ CPPUNIT_ASSERT(m->splits()[0].accountId() == "A000003");
+ CPPUNIT_ASSERT(m->splits()[1].accountId() == "A000002");
+ CPPUNIT_ASSERT(m->accountReferenced("A000001") == false);
+ CPPUNIT_ASSERT(m->accountReferenced("A000002") == true);
+ CPPUNIT_ASSERT(m->splits()[0].id() == "S0001");
+ CPPUNIT_ASSERT(m->splits()[1].id() == "S0002");
+
+ CPPUNIT_ASSERT(split.id() == "S0001");
+ CPPUNIT_ASSERT(split.accountId() == "A000003");
+
+ } catch(MyMoneyException *e) {
+ CPPUNIT_FAIL("Unexpected exception!");
+ delete e;
+ }
+}
+
+void MyMoneyTransactionTest::testDeleteSplits() {
+ testAddSplits();
+ MyMoneySplit split;
+
+ // add a third split
+ split.setAccountId("A000003");
+ split.setValue(MyMoneyMoney(300));
+ try {
+ m->addSplit(split);
+ } catch(MyMoneyException *e) {
+ CPPUNIT_FAIL("Unexpected exception!");
+ delete e;
+ }
+
+ split.setId("S00000000");
+ // this one should fail, because the ID is invalid
+ try {
+ m->modifySplit(split);
+ CPPUNIT_FAIL("Exception expected");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+
+ // set id to correct value, and check that it worked
+ split.setId("S0002");
+ try {
+ m->removeSplit(split);
+ CPPUNIT_ASSERT(m->splitCount() == 2);
+ CPPUNIT_ASSERT(m->splits()[0].accountId() == "A000001");
+ CPPUNIT_ASSERT(m->accountReferenced("A000002") == false);
+ CPPUNIT_ASSERT(m->accountReferenced("A000001") == true);
+ CPPUNIT_ASSERT(m->accountReferenced("A000003") == true);
+ CPPUNIT_ASSERT(m->splits()[0].id() == "S0001");
+
+ } catch(MyMoneyException *e) {
+ CPPUNIT_FAIL("Unexpected exception!");
+ delete e;
+ }
+
+ // set id to the other correct value, and check that it worked
+ split.setId("S0003");
+ try {
+ m->removeSplit(split);
+ CPPUNIT_ASSERT(m->splitCount() == 1);
+ CPPUNIT_ASSERT(m->accountReferenced("A000001") == true);
+ CPPUNIT_ASSERT(m->accountReferenced("A000003") == false);
+ CPPUNIT_ASSERT(m->splits()[0].id() == "S0001");
+
+ } catch(MyMoneyException *e) {
+ CPPUNIT_FAIL("Unexpected exception!");
+ delete e;
+ }
+}
+
+void MyMoneyTransactionTest::testDeleteAllSplits() {
+ testAddSplits();
+
+ try {
+ m->removeSplits();
+ CPPUNIT_ASSERT(m->splitCount() == 0);
+ } catch(MyMoneyException *e) {
+ CPPUNIT_FAIL("Unexpected exception!");
+ delete e;
+ }
+}
+
+void MyMoneyTransactionTest::testExtractSplit() {
+ testAddSplits();
+ MyMoneySplit split;
+
+ // this one should fail, as the account is not referenced by
+ // any split in the transaction
+ try {
+ split = m->splitByAccount(QString("A000003"));
+ CPPUNIT_FAIL("Exception expected");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+
+ // this one should be found
+ try {
+ split = m->splitByAccount(QString("A000002"));
+ CPPUNIT_ASSERT(split.id() == "S0002");
+
+ } catch(MyMoneyException *e) {
+ CPPUNIT_FAIL("Unexpected exception!");
+ delete e;
+ }
+
+ // this one should be found also
+ try {
+ split = m->splitByAccount(QString("A000002"), false);
+ CPPUNIT_ASSERT(split.id() == "S0001");
+ } catch(MyMoneyException *e) {
+ CPPUNIT_FAIL("Unexpected exception!");
+ delete e;
+ }
+}
+
+void MyMoneyTransactionTest::testSplitSum() {
+ CPPUNIT_ASSERT(m->splitSum().isZero());
+
+ testAddSplits();
+
+ MyMoneySplit s1, s2;
+
+ s1 = m->splits()[0];
+ s1.setValue(0);
+ s2 = m->splits()[1];
+ s2.setValue(0);
+
+ m->modifySplit(s1);
+ m->modifySplit(s2);
+ CPPUNIT_ASSERT(m->splitSum().isZero());
+
+ s1.setValue(1234);
+ m->modifySplit(s1);
+ CPPUNIT_ASSERT(m->splitSum() == MyMoneyMoney(1234));
+
+ s2.setValue(-1234);
+ m->modifySplit(s2);
+ CPPUNIT_ASSERT(m->splitSum().isZero());
+
+ s1.setValue(5678);
+ m->modifySplit(s1);
+ CPPUNIT_ASSERT(m->splitSum() == MyMoneyMoney(4444));
+}
+
+void MyMoneyTransactionTest::testIsLoanPayment() {
+ testAddSplits();
+ CPPUNIT_ASSERT(m->isLoanPayment() == false);
+
+ MyMoneySplit s1, s2;
+ s1 = m->splits()[0];
+ s2 = m->splits()[1];
+
+ s1.setAction(MyMoneySplit::ActionAmortization);
+ m->modifySplit(s1);
+ CPPUNIT_ASSERT(m->isLoanPayment() == true);
+ s1.setAction(MyMoneySplit::ActionWithdrawal);
+ m->modifySplit(s1);
+ CPPUNIT_ASSERT(m->isLoanPayment() == false);
+
+ s2.setAction(MyMoneySplit::ActionAmortization);
+ m->modifySplit(s2);
+ CPPUNIT_ASSERT(m->isLoanPayment() == true);
+ s2.setAction(MyMoneySplit::ActionWithdrawal);
+ m->modifySplit(s2);
+ CPPUNIT_ASSERT(m->isLoanPayment() == false);
+}
+
+void MyMoneyTransactionTest::testAddDuplicateAccount() {
+ testAddSplits();
+
+ MyMoneySplit split1, split2;
+ split1.setAccountId("A000001");
+ split2.setAccountId("A000002");
+ split1.setValue(100);
+ split2.setValue(200);
+
+ try {
+ CPPUNIT_ASSERT(m->accountReferenced("A000001") == true);
+ CPPUNIT_ASSERT(m->accountReferenced("A000002") == true);
+ m->addSplit(split1);
+ m->addSplit(split2);
+ CPPUNIT_ASSERT(m->splitCount() == 2);
+ CPPUNIT_ASSERT(m->splits()[0].accountId() == "A000001");
+ CPPUNIT_ASSERT(m->splits()[1].accountId() == "A000002");
+ CPPUNIT_ASSERT(m->accountReferenced("A000001") == true);
+ CPPUNIT_ASSERT(m->accountReferenced("A000002") == true);
+ CPPUNIT_ASSERT(m->splits()[0].id() == "S0001");
+ CPPUNIT_ASSERT(m->splits()[1].id() == "S0002");
+ CPPUNIT_ASSERT(split1.id() == "S0001");
+ CPPUNIT_ASSERT(split2.id() == "S0002");
+
+ } catch(MyMoneyException *e) {
+ unexpectedException(e);
+ }
+
+ CPPUNIT_ASSERT(m->splits()[0].value() == MyMoneyMoney(200));
+ CPPUNIT_ASSERT(m->splits()[1].value() == MyMoneyMoney(400));
+}
+
+void MyMoneyTransactionTest::testModifyDuplicateAccount() {
+ testAddSplits();
+ MyMoneySplit split;
+
+ split = m->splitByAccount(QString("A000002"));
+ split.setAccountId("A000001");
+ try {
+ m->modifySplit(split);
+ CPPUNIT_ASSERT(m->splitCount() == 1);
+ CPPUNIT_ASSERT(m->accountReferenced("A000001") == true);
+ CPPUNIT_ASSERT(m->splits()[0].value() == MyMoneyMoney(300));
+
+ } catch(MyMoneyException *e) {
+ unexpectedException(e);
+ }
+}
+void MyMoneyTransactionTest::testWriteXML() {
+ MyMoneyTransaction t;
+ t.setPostDate(QDate(2001,12,28));
+ t.setEntryDate(QDate(2003,9,29));
+ t.setId("T000000000000000001");
+ t.setMemo("Wohnung:Miete");
+ t.setCommodity("EUR");
+ t.setValue("key", "value");
+
+ MyMoneySplit s;
+ s.setPayeeId("P000001");
+ s.setShares(MyMoneyMoney(96379, 100));
+ s.setValue(MyMoneyMoney(96379, 100));
+ s.setAction(MyMoneySplit::ActionWithdrawal);
+ s.setAccountId("A000076");
+ s.setReconcileFlag(MyMoneySplit::Reconciled);
+ s.setBankID("SPID");
+ t.addSplit(s);
+
+ QDomDocument doc("TEST");
+ QDomElement el = doc.createElement("TRANSACTION-CONTAINER");
+ doc.appendChild(el);
+ t.writeXML(doc, el);
+
+ QString ref = QString(
+ "<!DOCTYPE TEST>\n"
+ "<TRANSACTION-CONTAINER>\n"
+ " <TRANSACTION postdate=\"2001-12-28\" memo=\"Wohnung:Miete\" id=\"T000000000000000001\" commodity=\"EUR\" entrydate=\"2003-09-29\" >\n"
+ " <SPLITS>\n"
+ " <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"96379/100\" action=\"Withdrawal\" bankid=\"SPID\" number=\"\" reconcileflag=\"2\" memo=\"\" value=\"96379/100\" id=\"S0001\" account=\"A000076\" />\n"
+ " </SPLITS>\n"
+ " <KEYVALUEPAIRS>\n"
+ " <PAIR key=\"key\" value=\"value\" />\n"
+ " </KEYVALUEPAIRS>\n"
+ " </TRANSACTION>\n"
+ "</TRANSACTION-CONTAINER>\n"
+
+ );
+
+ CPPUNIT_ASSERT(doc.toString() == ref);
+}
+
+void MyMoneyTransactionTest::testReadXML() {
+ MyMoneyTransaction t;
+
+ QString ref_ok = QString(
+ "<!DOCTYPE TEST>\n"
+ "<TRANSACTION-CONTAINER>\n"
+ " <TRANSACTION postdate=\"2001-12-28\" memo=\"Wohnung:Miete\" id=\"T000000000000000001\" commodity=\"EUR\" entrydate=\"2003-09-29\" >\n"
+ " <SPLITS>\n"
+ " <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"96379/100\" action=\"Withdrawal\" bankid=\"SPID\" number=\"\" reconcileflag=\"2\" memo=\"\" value=\"96379/100\" account=\"A000076\" />\n"
+ " </SPLITS>\n"
+ " <KEYVALUEPAIRS>\n"
+ " <PAIR key=\"key\" value=\"value\" />\n"
+ " </KEYVALUEPAIRS>\n"
+ " </TRANSACTION>\n"
+ "</TRANSACTION-CONTAINER>\n"
+ );
+
+ QString ref_false = QString(
+ "<!DOCTYPE TEST>\n"
+ "<TRANSACTION-CONTAINER>\n"
+ " <TRANS-ACTION postdate=\"2001-12-28\" memo=\"Wohnung:Miete\" id=\"T000000000000000001\" commodity=\"EUR\" entrydate=\"2003-09-29\" >\n"
+ " <SPLITS>\n"
+ " <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"96379/100\" action=\"Withdrawal\" bankid=\"SPID\" number=\"\" reconcileflag=\"2\" memo=\"\" value=\"96379/100\" account=\"A000076\" />\n"
+ " </SPLITS>\n"
+ " <KEYVALUEPAIRS>\n"
+ " <PAIR key=\"key\" value=\"value\" />\n"
+ " </KEYVALUEPAIRS>\n"
+ " </TRANS-ACTION>\n"
+ "</TRANSACTION-CONTAINER>\n"
+ );
+
+ QDomDocument doc;
+ QDomElement node;
+ doc.setContent(ref_false);
+ node = doc.documentElement().firstChild().toElement();
+
+ try {
+ t = MyMoneyTransaction(node);
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+
+ doc.setContent(ref_ok);
+ node = doc.documentElement().firstChild().toElement();
+
+ t.setValue("key", "VALUE");
+ try {
+ t = MyMoneyTransaction(node);
+ CPPUNIT_ASSERT(t.m_postDate == QDate(2001,12,28));
+ CPPUNIT_ASSERT(t.m_entryDate == QDate(2003,9,29));
+ CPPUNIT_ASSERT(t.id() == "T000000000000000001");
+ CPPUNIT_ASSERT(t.m_memo == "Wohnung:Miete");
+ CPPUNIT_ASSERT(t.m_commodity == "EUR");
+ CPPUNIT_ASSERT(t.pairs().count() == 1);
+ CPPUNIT_ASSERT(t.value("key") == "value");
+ CPPUNIT_ASSERT(t.splits().count() == 1);
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneyTransactionTest::testReadXMLEx()
+{
+ MyMoneyTransaction t;
+
+ QString ref_ok = QString(
+ "<!DOCTYPE TEST>\n"
+ "<TRANSACTION-CONTAINER>\n"
+ "<TRANSACTION postdate=\"2010-03-05\" memo=\"\" id=\"T000000000000004189\" commodity=\"EUR\" entrydate=\"2010-03-08\" >\n"
+ " <SPLITS>\n"
+ " <SPLIT payee=\"P000010\" reconciledate=\"\" shares=\"-125000/100\" action=\"Transfer\" bankid=\"A000076-2010-03-05-b6850c0-1\" number=\"\" reconcileflag=\"1\" memo=\"UMBUCHUNG\" value=\"-125000/100\" id=\"S0001\" account=\"A000076\" >\n"
+ " <KEYVALUEPAIRS>\n"
+ " <PAIR key=\"kmm-match-split\" value=\"S0002\" />\n"
+ " <PAIR key=\"kmm-matched-tx\" value=\"&amp;lt;!DOCTYPE MATCH>\n"
+ " &amp;lt;CONTAINER>\n"
+ " &amp;lt;TRANSACTION postdate=&quot;2010-03-05&quot; memo=&quot;UMBUCHUNG&quot; id=&quot;&quot; commodity=&quot;EUR&quot; entrydate=&quot;2010-03-08&quot; >\n"
+ " &amp;lt;SPLITS>\n"
+ " &amp;lt;SPLIT payee=&quot;P000010&quot; reconciledate=&quot;&quot; shares=&quot;125000/100&quot; action=&quot;Transfer&quot; bankid=&quot;&quot; number=&quot;&quot; reconcileflag=&quot;0&quot; memo=&quot;UMBUCHUNG&quot; value=&quot;125000/100&quot; id=&quot;S0001&quot; account=&quot;A000087&quot; />\n"
+ " &amp;lt;SPLIT payee=&quot;P000010&quot; reconciledate=&quot;&quot; shares=&quot;-125000/100&quot; action=&quot;&quot; bankid=&quot;A000076-2010-03-05-b6850c0-1&quot; number=&quot;&quot; reconcileflag=&quot;0&quot; memo=&quot;UMBUCHUNG&quot; value=&quot;-125000/100&quot; id=&quot;S0002&quot; account=&quot;A000076&quot; />\n"
+ " &amp;lt;/SPLITS>\n"
+ " &amp;lt;KEYVALUEPAIRS>\n"
+ " &amp;lt;PAIR key=&quot;Imported&quot; value=&quot;true&quot; />\n"
+ " &amp;lt;/KEYVALUEPAIRS>\n"
+ " &amp;lt;/TRANSACTION>\n"
+ " &amp;lt;/CONTAINER>\n"
+ "\" />\n"
+ " <PAIR key=\"kmm-orig-memo\" value=\"\" />\n"
+ " </KEYVALUEPAIRS>\n"
+ " </SPLIT>\n"
+ " <SPLIT payee=\"P000010\" reconciledate=\"\" shares=\"125000/100\" action=\"Transfer\" bankid=\"\" number=\"\" reconcileflag=\"0\" memo=\"\" value=\"125000/100\" id=\"S0002\" account=\"A000087\" />\n"
+ " </SPLITS>\n"
+ "</TRANSACTION>\n"
+ "</TRANSACTION-CONTAINER>\n"
+ );
+ QDomDocument doc;
+ QDomElement node;
+ doc.setContent(ref_ok);
+ node = doc.documentElement().firstChild().toElement();
+
+ try {
+ t = MyMoneyTransaction(node);
+ CPPUNIT_ASSERT(t.pairs().count() == 0);
+ CPPUNIT_ASSERT(t.splits().count() == 2);
+ CPPUNIT_ASSERT(t.splits()[0].pairs().count() == 3);
+ CPPUNIT_ASSERT(t.splits()[1].pairs().count() == 0);
+ CPPUNIT_ASSERT(t.splits()[0].isMatched());
+
+ MyMoneyTransaction ti = t.splits()[0].matchedTransaction();
+ CPPUNIT_ASSERT(ti.pairs().count() == 1);
+ CPPUNIT_ASSERT(ti.isImported());
+ CPPUNIT_ASSERT(ti.splits().count() == 2);
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+}
+
+void MyMoneyTransactionTest::testHasReferenceTo()
+{
+ MyMoneyTransaction t;
+ t.setPostDate(QDate(2001,12,28));
+ t.setEntryDate(QDate(2003,9,29));
+ t.setId("T000000000000000001");
+ t.setMemo("Wohnung:Miete");
+ t.setCommodity("EUR");
+ t.setValue("key", "value");
+
+ MyMoneySplit s;
+ s.setPayeeId("P000001");
+ s.setShares(MyMoneyMoney(96379, 100));
+ s.setValue(MyMoneyMoney(96379, 100));
+ s.setAction(MyMoneySplit::ActionWithdrawal);
+ s.setAccountId("A000076");
+ s.setReconcileFlag(MyMoneySplit::Reconciled);
+ t.addSplit(s);
+
+ CPPUNIT_ASSERT(t.hasReferenceTo("EUR") == true);
+ CPPUNIT_ASSERT(t.hasReferenceTo("P000001") == true);
+ CPPUNIT_ASSERT(t.hasReferenceTo("A000076") == true);
+}
+
+void MyMoneyTransactionTest::testAutoCalc()
+{
+ CPPUNIT_ASSERT(m->hasAutoCalcSplit() == false);
+ testAddSplits();
+ CPPUNIT_ASSERT(m->hasAutoCalcSplit() == false);
+ MyMoneySplit split;
+
+ split = m->splits()[0];
+ split.setShares(MyMoneyMoney::autoCalc);
+ split.setValue(MyMoneyMoney::autoCalc);
+ m->modifySplit(split);
+
+ CPPUNIT_ASSERT(m->hasAutoCalcSplit() == true);
+}
+
+void MyMoneyTransactionTest::testIsStockSplit()
+{
+ CPPUNIT_ASSERT(m->isStockSplit() == false);
+ testAddSplits();
+ CPPUNIT_ASSERT(m->isStockSplit() == false);
+ m->removeSplits();
+ MyMoneySplit s;
+ s.setShares(MyMoneyMoney(1,2));
+ s.setAction(MyMoneySplit::ActionSplitShares);
+ s.setAccountId("A0001");
+ m->addSplit(s);
+ CPPUNIT_ASSERT(m->isStockSplit() == true);
+}
+
+void MyMoneyTransactionTest::testAddMissingAccountId()
+{
+ MyMoneySplit s;
+ s.setShares(MyMoneyMoney(1,2));
+ try {
+ m->addSplit(s);
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+}
+
+void MyMoneyTransactionTest::testModifyMissingAccountId()
+{
+ testAddSplits();
+ MyMoneySplit s = m->splits()[0];
+ s.setAccountId(QString());
+
+ try {
+ m->modifySplit(s);
+ CPPUNIT_FAIL("Missing expected exception");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+}
+
diff --git a/kmymoney2/mymoney/mymoneytransactiontest.h b/kmymoney2/mymoney/mymoneytransactiontest.h
new file mode 100644
index 0000000..c2bb0da
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneytransactiontest.h
@@ -0,0 +1,96 @@
+/***************************************************************************
+ mymoneytransactiontest.h
+ -------------------
+ copyright : (C) 2002 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 __MYMONEYTRANSACTIONTEST_H__
+#define __MYMONEYTRANSACTIONTEST_H__
+
+/*
+#include <cppunit/TestCaller.h>
+#include <cppunit/TestCase.h>
+#include <cppunit/TestSuite.h>
+*/
+#include <cppunit/extensions/HelperMacros.h>
+#include "autotest.h"
+
+#define private public
+#define protected public
+#include "mymoneytransaction.h"
+#undef private
+
+class MyMoneyTransactionTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(MyMoneyTransactionTest);
+ CPPUNIT_TEST(testEmptyConstructor);
+ CPPUNIT_TEST(testSetFunctions);
+ CPPUNIT_TEST(testConstructor);
+ CPPUNIT_TEST(testCopyConstructor);
+ CPPUNIT_TEST(testAssignmentConstructor);
+ CPPUNIT_TEST(testAddSplits);
+ CPPUNIT_TEST(testModifySplits);
+ CPPUNIT_TEST(testDeleteSplits);
+ CPPUNIT_TEST(testDeleteAllSplits);
+ CPPUNIT_TEST(testEquality);
+ CPPUNIT_TEST(testInequality);
+ CPPUNIT_TEST(testExtractSplit);
+ CPPUNIT_TEST(testSplitSum);
+ CPPUNIT_TEST(testIsLoanPayment);
+ CPPUNIT_TEST(testWriteXML);
+ CPPUNIT_TEST(testReadXML);
+ CPPUNIT_TEST(testReadXMLEx);
+ CPPUNIT_TEST(testAutoCalc);
+ CPPUNIT_TEST(testHasReferenceTo);
+ CPPUNIT_TEST(testIsStockSplit);
+ CPPUNIT_TEST(testAddMissingAccountId);
+ CPPUNIT_TEST(testModifyMissingAccountId);
+#if 0
+ CPPUNIT_TEST(testAddDuplicateAccount);
+ CPPUNIT_TEST(testModifyDuplicateAccount);
+#endif
+ CPPUNIT_TEST_SUITE_END();
+
+protected:
+ MyMoneyTransaction *m;
+
+public:
+ MyMoneyTransactionTest ();
+
+ void setUp ();
+ void tearDown ();
+ void testEmptyConstructor();
+ void testSetFunctions();
+ void testConstructor();
+ void testCopyConstructor();
+ void testAssignmentConstructor();
+ void testEquality();
+ void testInequality();
+ void testAddSplits();
+ void testModifySplits();
+ void testDeleteSplits();
+ void testExtractSplit();
+ void testDeleteAllSplits();
+ void testSplitSum();
+ void testIsLoanPayment();
+ void testAddDuplicateAccount();
+ void testModifyDuplicateAccount();
+ void testWriteXML();
+ void testReadXML();
+ void testReadXMLEx();
+ void testAutoCalc();
+ void testHasReferenceTo();
+ void testIsStockSplit();
+ void testAddMissingAccountId();
+ void testModifyMissingAccountId();
+};
+#endif
diff --git a/kmymoney2/mymoney/mymoneyutils.cpp b/kmymoney2/mymoney/mymoneyutils.cpp
new file mode 100644
index 0000000..3739f9a
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyutils.cpp
@@ -0,0 +1,339 @@
+/***************************************************************************
+ mymoneyutils.cpp - description
+ -------------------
+ begin : Tue Jan 29 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 <iostream>
+
+#include "mymoneyutils.h"
+#include "mymoneyaccount.h"
+
+#include <cstdio>
+#include <cstdarg>
+#include <cstdlib>
+
+#include <qregexp.h>
+
+#ifdef _CHECK_MEMORY
+
+#undef new
+#undef _CheckMemory_Leak
+#undef _CheckMemory_FreeAll
+
+_CheckMemory chkmem;
+bool enable=false;
+
+_CheckMemoryEntry::_CheckMemoryEntry(void *p, int line, size_t size, const char *file)
+ : m_p(p), m_line(line), m_size(size), m_file(file)
+{
+}
+
+_CheckMemoryEntry::_CheckMemoryEntry()
+ : m_p(0), m_line(0), m_size(0)
+{
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+_CheckMemory::_CheckMemory()
+{
+ outfunc = (_CheckMemoryOutFunc *)NULL;
+}
+
+_CheckMemory::_CheckMemory(_CheckMemoryOutFunc *out)
+{
+ outfunc = out;
+}
+
+_CheckMemory::~_CheckMemory()
+{
+}
+
+_CheckMemoryOutFunc *_CheckMemory::SetOutFunc(_CheckMemoryOutFunc *out)
+{
+ _CheckMemoryOutFunc *old;
+ old = outfunc;
+ outfunc = out;
+ return old;
+}
+
+void _CheckMemory::Output(const char *fmt,...)
+{
+ va_list args;
+ char buf[128];
+ va_start(args,fmt);
+ if(outfunc) {
+ vsprintf(buf,fmt,args);
+ outfunc(buf);
+ }
+ else {
+ vfprintf(stderr,fmt,args);
+ putc('\n', stderr);
+ }
+ va_end(args);
+}
+
+int _CheckMemory::TableCount(void)
+{
+ return table.size();
+}
+
+bool _CheckMemory::CheckMemoryLeak(bool freeall)
+{
+ bool d = false;
+ size_t total = 0;
+ int freec = 0;
+ CheckMemoryTable::ConstIterator it;
+
+ for(it = table.begin(); it != table.end(); ++it) {
+ if((*it).pointer() != 0) {
+ total += (*it).size();
+ freec++;
+ if(d == false) {
+ Output("CheckMemory++: CheckMemoryLeak: Memory leak detected!");
+ Output("Position |Size(bytes) |Allocated at");
+ d=true;
+ }
+ if(d==true)
+ Output("%p |%-13d|%s:%d",(*it).pointer(),(int)(*it).size(),(*it).file(),(*it).line());
+ }
+ }
+ if(d == true)
+ Output("You have forgotten to free %d object(s), %d bytes of memory.",freec, (int)total);
+ else
+ Output("CheckMemory++: CheckMemoryLeak: No memory leak detected.");
+ if(freeall == true)
+ FreeAll();
+ return true;
+}
+
+void _CheckMemory::FreeAll()
+{
+ size_t total=0;
+ int freec=0;
+ CheckMemoryTable::Iterator it;
+
+ for(it = table.begin(); it != table.end(); it = table.begin()) {
+ if((*it).pointer() != 0) {
+ total += (*it).size();
+ freec++;
+ Output("CheckMemory++: FreeAll: freed %d bytes of memory at %p.",(int)(*it).size(),(*it).pointer());
+ free((*it).pointer());
+ }
+ table.remove(it);
+ }
+ Output("CheckMemory++: FreeAll: Totally freed %d objects, %d bytes of memory.",freec,(int)total);
+}
+
+void _CheckMemory_Init(_CheckMemoryOutFunc *out)
+{
+ if(enable!=true) {
+ chkmem.Restart();
+ chkmem.SetOutFunc(out);
+ enable=true;
+ }
+}
+
+void _CheckMemory_End()
+{
+ if(enable!=false) {
+ chkmem.Restart();
+ chkmem.SetOutFunc(NULL);
+ enable=false;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void *operator new(size_t s,const char *file,int line) throw()
+{
+ void *p = malloc(s);
+
+ if(p == NULL) throw;
+ if(enable==true) {
+ _CheckMemoryEntry entry(p, line, s, file);
+ chkmem.table[p] = entry;
+ }
+ return p;
+}
+
+void * operator new [] (size_t s,const char *file,int line) throw()
+{
+ void *p = malloc(s);
+
+ if(p == NULL) throw;
+ if(enable==true) {
+ _CheckMemoryEntry entry(p, line, s, file);
+ chkmem.table[p] = entry;
+ }
+ return p;
+}
+
+void operator delete(void *p) throw()
+{
+ if(enable==true) {
+ CheckMemoryTable::Iterator it;
+ it = chkmem.table.find(p);
+ if(it != chkmem.table.end()) {
+ chkmem.table.remove(it);
+ }
+ }
+ free(p);
+}
+
+void operator delete [] (void *p) throw()
+{
+ if(enable==true) {
+ CheckMemoryTable::Iterator it;
+ it = chkmem.table.find(p);
+ if(it != chkmem.table.end()) {
+ chkmem.table.remove(it);
+ }
+ }
+ free(p);
+}
+
+#endif // _CHECK_MEMORY
+
+QString MyMoneyUtils::getFileExtension(QString strFileName)
+{
+ QString strTemp;
+ if(!strFileName.isEmpty())
+ {
+ //find last . delminator
+ int nLoc = strFileName.findRev('.');
+ if(nLoc != -1)
+ {
+ strTemp = strFileName.right(strFileName.length() - (nLoc + 1));
+ return strTemp.upper();
+ }
+ }
+ return strTemp;
+}
+
+int MyMoneyTracer::m_indentLevel = 0;
+int MyMoneyTracer::m_onoff = 0;
+
+MyMoneyTracer::MyMoneyTracer(const char* name)
+{
+ if(m_onoff) {
+ QRegExp exp("(.*)::(.*)");
+ if(exp.search(name) != -1) {
+ m_className = exp.cap(1);
+ m_memberName = exp.cap(2);
+ } else {
+ m_className = QString(name);
+ m_memberName = QString();
+ }
+ QString indent;
+ indent.fill(' ', m_indentLevel);
+ std::cerr << indent.latin1() << "ENTER: " << m_className.latin1() << "::" << m_memberName.latin1() << std::endl;
+ }
+ m_indentLevel += 2;
+}
+
+MyMoneyTracer::MyMoneyTracer(const QString& className, const QString& memberName) :
+ m_className(className),
+ m_memberName(memberName)
+{
+ if(m_onoff) {
+ QString indent;
+ indent.fill(' ', m_indentLevel);
+ std::cerr << indent.latin1() << "ENTER: " << m_className.latin1() << "::" << m_memberName.latin1() << std::endl;
+ }
+ m_indentLevel += 2;
+}
+
+MyMoneyTracer::~MyMoneyTracer()
+{
+ m_indentLevel -= 2;
+ if(m_onoff) {
+ QString indent;
+ indent.fill(' ', m_indentLevel);
+ std::cerr << indent.latin1() << "LEAVE: " << m_className.latin1() << "::" << m_memberName.latin1() << std::endl;
+ }
+}
+
+void MyMoneyTracer::printf(const char *format, ...) const
+{
+ if(m_onoff) {
+ va_list args;
+ va_start(args, format);
+ QString indent;
+ indent.fill(' ', m_indentLevel);
+ std::cerr << indent.latin1();
+
+ vfprintf(stderr,format,args);
+ putc('\n', stderr);
+ va_end(args);
+ }
+}
+
+void MyMoneyTracer::onOff(int onOff)
+{
+ m_onoff = onOff;
+}
+
+void MyMoneyTracer::on(void)
+{
+ m_onoff = 1;
+}
+
+void MyMoneyTracer::off(void)
+{
+ m_onoff = 0;
+}
+
+QString dateToString(const QDate& date)
+{
+ if(!date.isNull() && date.isValid())
+ return date.toString(Qt::ISODate);
+
+ return QString();
+}
+
+QDate stringToDate(const QString& str)
+{
+ if(str.length()) {
+ QDate date = QDate::fromString(str, Qt::ISODate);
+ if(!date.isNull() && date.isValid())
+ return date;
+ }
+ return QDate();
+}
+
+QString QStringEmpty(const QString& val)
+{
+ if(!val.isEmpty())
+ return QString(val);
+
+ return QString();
+}
+
+unsigned long extractId(const QString& txt)
+{
+ int pos;
+ unsigned long rc = 0;
+
+ pos = txt.find(QRegExp("\\d+"), 0);
+ if(pos != -1) {
+ rc = atol(txt.mid(pos));
+ }
+ return rc;
+}
diff --git a/kmymoney2/mymoney/mymoneyutils.h b/kmymoney2/mymoney/mymoneyutils.h
new file mode 100644
index 0000000..12bb95e
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyutils.h
@@ -0,0 +1,192 @@
+/***************************************************************************
+ mymoneyutils.h - description
+ -------------------
+ begin : Tue Jan 29 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 _MYMONEYUTILS_H_
+#define _MYMONEYUTILS_H_
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <qstring.h>
+#include <qdatetime.h>
+#include <qvaluelist.h>
+#include <kmymoney/export.h>
+#if 0
+
+//Includes for STL support below
+#include <vector>
+#include <map>
+#include <list>
+#include <string>
+using namespace std;
+
+#ifdef _UNICODE
+typedef std::wstring String;
+#else
+typedef std::string String;
+#endif
+
+#else
+
+//typedef for data type to store currency with.
+typedef long long DLONG;
+
+typedef QString String;
+#endif // 0
+
+void timetrace(const char *);
+void timestamp(const char *);
+
+//class that has utility functions to use throughout the application.
+class MyMoneyUtils
+{
+public:
+ MyMoneyUtils() {}
+ ~MyMoneyUtils() {}
+
+ //static function to add the correct file extension at the end of the file name
+ static QString getFileExtension(QString strFileName);
+
+};
+
+class KMYMONEY_EXPORT MyMoneyTracer
+{
+public:
+ MyMoneyTracer(const char* prettyName);
+#define MYMONEYTRACER(a) MyMoneyTracer a(__PRETTY_FUNCTION__)
+
+ MyMoneyTracer(const QString& className, const QString& methodName);
+ ~MyMoneyTracer();
+
+ /**
+ * This method allows to trace a printf like formatted text
+ *
+ * @param format format mask
+ */
+ void printf(const char *format, ...) const __attribute__ ((format (__printf__, 2, 3)));
+
+ static void off(void);
+ static void on(void);
+ static void onOff(int onOff);
+
+private:
+ QString m_className;
+ QString m_memberName;
+
+ static int m_indentLevel;
+ static int m_onoff;
+};
+
+#ifdef _CHECK_MEMORY
+
+#include <cstddef>
+#include <qmap.h>
+
+class _CheckMemoryEntry {
+public:
+ _CheckMemoryEntry();
+ _CheckMemoryEntry(void *p, int line, size_t size, const char *file);
+ ~_CheckMemoryEntry() {}
+
+ void * pointer(void) const { return m_p; };
+ int line(void) const { return m_line; };
+ size_t size(void) const { return m_size; };
+ const char* file(void) const { return m_file; };
+
+private:
+ void *m_p;
+ int m_line;
+ size_t m_size;
+ QString m_file;
+};
+
+typedef QMap<void *, _CheckMemoryEntry> CheckMemoryTable;
+
+typedef void _CheckMemoryOutFunc(const char *);
+
+class KMYMONEY_EXPORT _CheckMemory {
+ public:
+ _CheckMemory();
+ _CheckMemory(_CheckMemoryOutFunc *out);
+ virtual ~_CheckMemory();
+
+ _CheckMemoryOutFunc *SetOutFunc(_CheckMemoryOutFunc *out);
+ bool CheckMemoryLeak(bool freeall);
+ void FreeAll();
+ inline void Restart() { table.clear(); };
+
+ int TableCount(void);
+
+ private:
+ void Output(const char *fmt,...) __attribute__ ((format (__printf__, 2, 3)));
+
+ CheckMemoryTable table;
+ _CheckMemoryOutFunc *outfunc;
+
+ friend void * operator new(size_t s,const char *file,int line) throw();
+ friend void * operator new [] (size_t,const char *file,int line) throw();
+ friend void operator delete(void *p) throw();
+ friend void operator delete [] (void *p) throw();
+};
+
+KMYMONEY_EXPORT void * operator new(size_t s,const char *file,int line) throw(); // Normal new operator
+KMYMONEY_EXPORT void * operator new [] (size_t s,const char *file,int line) throw(); // Array new operator
+KMYMONEY_EXPORT void operator delete(void *p) throw();
+KMYMONEY_EXPORT void operator delete [] (void *p) throw();
+
+#define new new(__FILE__,__LINE__)
+
+KMYMONEY_EXPORT extern _CheckMemory chkmem;
+
+KMYMONEY_EXPORT void _CheckMemory_Init(_CheckMemoryOutFunc *out);
+KMYMONEY_EXPORT void _CheckMemory_End();
+#define _CheckMemory_Leak(freeall) chkmem.CheckMemoryLeak(freeall)
+#define _CheckMemory_FreeAll() chkmem.FreeAll()
+
+#endif // _CHECK_MEMORY
+
+/**
+ * This function returns a date in the form specified by Qt::ISODate.
+ * If the @p date is invalid an empty string will be returned.
+ *
+ * @param date const reference to date to be converted
+ * @return QString containing the converted date
+ */
+KMYMONEY_EXPORT QString dateToString(const QDate& date);
+
+/**
+ * This function returns a date as QDate object as specified by
+ * the parameter @p str. @p str must be in Qt::ISODate format.
+ * If @p str is empty or contains an invalid date, QDate() is
+ * returned.
+ *
+ * @param str date in Qt::ISODate format
+ * @return QDate object
+ */
+KMYMONEY_EXPORT QDate stringToDate(const QString& str);
+
+KMYMONEY_EXPORT QString QStringEmpty(const QString&);
+
+KMYMONEY_EXPORT unsigned long extractId(const QString& txt);
+
+#endif
diff --git a/kmymoney2/mymoney/storage/Makefile.am b/kmymoney2/mymoney/storage/Makefile.am
new file mode 100644
index 0000000..0055800
--- /dev/null
+++ b/kmymoney2/mymoney/storage/Makefile.am
@@ -0,0 +1,20 @@
+KDE_OPTIONS = noautodist
+
+INCLUDES = $(all_includes) -I$(top_srcdir) -I. -I$(top_builddir)/kmymoney2/dialogs
+
+noinst_LIBRARIES = libstorage.a
+libstorage_a_METASOURCES = AUTO
+
+libstorage_a_SOURCES = imymoneystorageformat.cpp mymoneystoragexml.cpp mymoneystoragedump.cpp mymoneyseqaccessmgr.cpp mymoneydatabasemgr.cpp imymoneystorage.cpp imymoneyserialize.cpp mymoneystorageanon.cpp mymoneystoragesql.cpp
+
+instdir=$(includedir)/kmymoney
+inst_HEADERS = imymoneystorage.h imymoneyserialize.h imymoneystorageformat.h
+
+noinst_HEADERS = mymoneyseqaccessmgr.h mymoneydatabasemgr.h mymoneystorageanon.h mymoneystoragedump.h mymoneystoragexml.h mymoneyseqaccessmgrtest.h mymoneydatabasemgrtest.h mymoneystoragesql.h mymoneystoragebin.h mymoneymap.h mymoneymaptest.h
+
+if CPPUNIT
+check_LIBRARIES = libstoragetest.a
+
+libstoragetest_a_SOURCES = mymoneyseqaccessmgrtest.cpp mymoneymaptest.cpp mymoneydatabasemgrtest.cpp
+endif
+
diff --git a/kmymoney2/mymoney/storage/imymoneyserialize.cpp b/kmymoney2/mymoney/storage/imymoneyserialize.cpp
new file mode 100644
index 0000000..14b76dd
--- /dev/null
+++ b/kmymoney2/mymoney/storage/imymoneyserialize.cpp
@@ -0,0 +1,31 @@
+/***************************************************************************
+ imymoneyserialize.cpp - description
+ -------------------
+ begin : Fri May 10 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "imymoneyserialize.h"
+
+IMyMoneySerialize::IMyMoneySerialize()
+{
+}
+IMyMoneySerialize::~IMyMoneySerialize()
+{
+}
+
diff --git a/kmymoney2/mymoney/storage/imymoneyserialize.h b/kmymoney2/mymoney/storage/imymoneyserialize.h
new file mode 100644
index 0000000..a0c12ca
--- /dev/null
+++ b/kmymoney2/mymoney/storage/imymoneyserialize.h
@@ -0,0 +1,374 @@
+/***************************************************************************
+ imymoneyserialize.h - description
+ -------------------
+ begin : Fri May 10 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 IMYMONEYSERIALIZE_H
+#define IMYMONEYSERIALIZE_H
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstring.h>
+#include <qvaluelist.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include <kmymoney/mymoneyutils.h>
+#include <kmymoney/mymoneyinstitution.h>
+#include <kmymoney/mymoneyaccount.h>
+#include <kmymoney/mymoneytransaction.h>
+#include <kmymoney/mymoneypayee.h>
+#include <kmymoney/mymoneyscheduled.h>
+#include <kmymoney/mymoneytransactionfilter.h>
+#include <kmymoney/mymoneysecurity.h>
+#include <kmymoney/mymoneyprice.h>
+#include <kmymoney/mymoneyreport.h>
+#include <kmymoney/mymoneybudget.h>
+#include "mymoneystoragesql.h"
+
+/**
+ * @author Thomas Baumgart
+ */
+
+/**
+ * This class represents the interface to serialize a MyMoneyStorage object
+ */
+class IMyMoneySerialize {
+public:
+ IMyMoneySerialize();
+ virtual ~IMyMoneySerialize();
+
+ // general get functions
+ virtual const MyMoneyPayee user(void) const = 0;
+ virtual const QDate creationDate(void) const = 0;
+ virtual const QDate lastModificationDate(void) const = 0;
+ virtual unsigned int currentFixVersion(void) const = 0;
+ virtual unsigned int fileFixVersion(void) const = 0;
+
+ // general set functions
+ virtual void setUser(const MyMoneyPayee& val) = 0;
+ virtual void setCreationDate(const QDate& val) = 0;
+ virtual void setFileFixVersion(const unsigned int v) = 0;
+ /**
+ * This method is used to get a SQL reader for subsequent database access
+ */
+ virtual KSharedPtr <MyMoneyStorageSql> connectToDatabase
+ (const KURL& url) = 0;
+ /**
+ * This method is used when a database file is open, and the data is to
+ * be saved in a different file or format. It will ensure that all data
+ * from the database is available in memory to enable it to be written.
+ */
+ virtual void fillStorage() = 0;
+
+ /**
+ * This method is used to set the last modification date of
+ * the storage object. It also clears the dirty flag and should
+ * therefor be called as last operation when loading from a
+ * file.
+ *
+ * @param val QDate of last modification
+ */
+ virtual void setLastModificationDate(const QDate& val) = 0;
+
+ /**
+ * This method returns a list of accounts inside the storage object.
+ *
+ * @param list reference to QValueList receiving the account objects
+ *
+ * @note The standard accounts will not be returned
+ */
+ virtual void accountList(QValueList<MyMoneyAccount>& list) const = 0;
+
+ /**
+ * This method returns a list of the institutions
+ * inside a MyMoneyStorage object
+ *
+ * @return QMap containing the institution information
+ */
+ virtual const QValueList<MyMoneyInstitution> institutionList(void) const = 0;
+
+ /**
+ * This method is used to pull a list of transactions from the file
+ * global transaction pool. It returns all those transactions
+ * that match the filter passed as argument. If the filter is empty,
+ * the whole journal will be returned.
+ *
+ * @param list reference to QValueList<MyMoneyTransaction> receiving
+ * the set of transactions
+ * @param filter MyMoneyTransactionFilter object with the match criteria
+ */
+ virtual void transactionList(QValueList<MyMoneyTransaction>& list, MyMoneyTransactionFilter& filter) const = 0;
+
+
+ /**
+ * This method returns whether a given transaction is already in memory, to avoid
+ * reloading it from the database
+ */
+ virtual bool isDuplicateTransaction(const QString&) const = 0;
+ /**
+ * This method returns a list of the payees
+ * inside a MyMoneyStorage object
+ *
+ * @return QValueList<MyMoneyPayee> containing the payee information
+ */
+ virtual const QValueList<MyMoneyPayee> payeeList(void) const = 0;
+
+ /**
+ * This method returns a list of the scheduled transactions
+ * inside a MyMoneyStorage object. In order to retrieve a complete
+ * list of the transactions, all arguments should be used with their
+ * default arguments.
+ */
+ virtual const QValueList<MyMoneySchedule> scheduleList(const QString& = QString(),
+ const MyMoneySchedule::typeE = MyMoneySchedule::TYPE_ANY,
+ const MyMoneySchedule::occurenceE = MyMoneySchedule::OCCUR_ANY,
+ const MyMoneySchedule::paymentTypeE = MyMoneySchedule::STYPE_ANY,
+ const QDate& = QDate(),
+ const QDate& = QDate(),
+ const bool = false) const = 0;
+
+ /**
+ * This method returns a list of security objects that the engine has
+ * knowledge of.
+ */
+ virtual const QValueList<MyMoneySecurity> securityList(void) const = 0;
+
+ /**
+ * This method is used to return the standard liability account
+ * @return MyMoneyAccount liability account(group)
+ */
+ virtual const MyMoneyAccount liability(void) const = 0;
+
+ /**
+ * This method is used to return the standard asset account
+ * @return MyMoneyAccount asset account(group)
+ */
+ virtual const MyMoneyAccount asset(void) const = 0;
+
+ /**
+ * This method is used to return the standard expense account
+ * @return MyMoneyAccount expense account(group)
+ */
+ virtual const MyMoneyAccount expense(void) const = 0;
+
+ /**
+ * This method is used to return the standard income account
+ * @return MyMoneyAccount income account(group)
+ */
+ virtual const MyMoneyAccount income(void) const = 0;
+
+ /**
+ * This method is used to return the standard equity account
+ * @return MyMoneyAccount equity account(group)
+ */
+ virtual const MyMoneyAccount equity(void) const = 0;
+
+ /**
+ * This method is used to create a new account
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param account MyMoneyAccount filled with data
+ */
+ virtual void addAccount(MyMoneyAccount& account) = 0;
+
+ /**
+ * This method is used to add one account as sub-ordinate to another
+ * (parent) account. The objects that are passed will be modified
+ * accordingly.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param parent parent account the account should be added to
+ * @param account the account to be added
+ *
+ * @deprecated This method is only provided as long as we provide
+ * the version 0.4 binary reader. As soon as we deprecate
+ * this compatability mode this method will disappear from
+ * this interface!
+ */
+ virtual void addAccount(MyMoneyAccount& parent, MyMoneyAccount& account) = 0;
+
+ /**
+ * This method is used to create a new payee
+ *
+ * An exception will be thrown upon error conditions
+ *
+ * @param payee MyMoneyPayee reference to payee information
+ *
+ * @deprecated This method is only provided as long as we provide
+ * the version 0.4 binary reader. As soon as we deprecate
+ * this compatability mode this method will disappear from
+ * this interface!
+ *
+ */
+ virtual void addPayee(MyMoneyPayee& payee) = 0;
+
+ /**
+ * Adds an institution to the storage. A
+ * respective institution-ID will be generated within this record.
+ * The ID is stored as QString in the object passed as argument.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param institution The complete institution information in a
+ * MyMoneyInstitution object
+ *
+ * @deprecated This method is only provided as long as we provide
+ * the version 0.4 binary reader. As soon as we deprecate
+ * this compatability mode this method will disappear from
+ * this interface!
+ */
+ virtual void addInstitution(MyMoneyInstitution& institution) = 0;
+
+ /**
+ * Adds a transaction to the file-global transaction pool. A respective
+ * transaction-ID will be generated within this record. The ID is stored
+ * as QString with the object.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param transaction reference to the transaction
+ * @param skipAccountUpdate if set, the transaction lists of the accounts
+ * referenced in the splits are not updated. This is used for
+ * bulk loading a lot of transactions but not during normal operation.
+ * Refreshing the account's transaction list can be done using
+ * refreshAllAccountTransactionList().
+ *
+ * @deprecated This method is only provided as long as we provide
+ * the version 0.4 binary reader. As soon as we deprecate
+ * this compatability mode this method will disappear from
+ * this interface!
+ */
+ virtual void addTransaction(MyMoneyTransaction& transaction, const bool skipAccountUpdate = false) = 0;
+
+ virtual void loadAccounts(const QMap<QString, MyMoneyAccount>& map) = 0;
+ virtual void loadTransactions(const QMap<QString, MyMoneyTransaction>& map) = 0;
+ virtual void loadInstitutions(const QMap<QString, MyMoneyInstitution>& map) = 0;
+ virtual void loadPayees(const QMap<QString, MyMoneyPayee>& map) = 0;
+ virtual void loadSchedules(const QMap<QString, MyMoneySchedule>& map) = 0;
+ virtual void loadSecurities(const QMap<QString, MyMoneySecurity>& map) = 0;
+ virtual void loadCurrencies(const QMap<QString, MyMoneySecurity>& map) = 0;
+ virtual void loadReports( const QMap<QString, MyMoneyReport>& reports ) = 0;
+ virtual void loadBudgets( const QMap<QString, MyMoneyBudget>& budgets ) = 0;
+ virtual void loadPrices(const MyMoneyPriceList& list) = 0;
+
+ virtual unsigned long accountId(void) const = 0;
+ virtual unsigned long transactionId(void) const = 0;
+ virtual unsigned long payeeId(void) const = 0;
+ virtual unsigned long institutionId(void) const = 0;
+ virtual unsigned long scheduleId(void) const = 0;
+ virtual unsigned long securityId(void) const = 0;
+ virtual unsigned long reportId(void) const = 0;
+ virtual unsigned long budgetId(void) const = 0;
+
+ virtual void loadAccountId(const unsigned long id) = 0;
+ virtual void loadTransactionId(const unsigned long id) = 0;
+ virtual void loadPayeeId(const unsigned long id) = 0;
+ virtual void loadInstitutionId(const unsigned long id) = 0;
+ virtual void loadScheduleId(const unsigned long id) = 0;
+ virtual void loadSecurityId(const unsigned long id) = 0;
+ virtual void loadReportId(const unsigned long id) = 0;
+ virtual void loadBudgetId(const unsigned long id) = 0;
+
+ /**
+ * This method is used to retrieve the whole set of key/value pairs
+ * from the container. It is meant to be used for permanent storage
+ * functionality. See MyMoneyKeyValueContainer::pairs() for details.
+ *
+ * @return QMap<QString, QString> containing all key/value pairs of
+ * this container.
+ */
+ virtual const QMap<QString, QString> pairs(void) const = 0;
+
+ /**
+ * This method is used to initially store a set of key/value pairs
+ * in the container. It is meant to be used for loading functionality
+ * from permanent storage. See MyMoneyKeyValueContainer::setPairs()
+ * for details
+ *
+ * @param list const QMap<QString, QString> containing the set of
+ * key/value pairs to be loaded into the container.
+ *
+ * @note All existing key/value pairs in the container will be deleted.
+ */
+ virtual void setPairs(const QMap<QString, QString>& list) = 0;
+
+ virtual const QValueList<MyMoneySchedule> scheduleListEx( int scheduleTypes,
+ int scheduleOcurrences,
+ int schedulePaymentTypes,
+ QDate startDate,
+ const QStringList& accounts=QStringList()) const = 0;
+
+ /**
+ * This method is used to retrieve the list of all currencies
+ * known to the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @return QValueList of all MyMoneySecurity objects representing a currency.
+ */
+ virtual const QValueList<MyMoneySecurity> currencyList(void) const = 0;
+
+ /**
+ * This method is used to retrieve the list of all reports
+ * known to the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @return QValueList of all MyMoneyReport objects.
+ */
+ virtual const QValueList<MyMoneyReport> reportList( void ) const = 0;
+
+ /**
+ * This method is used to retrieve the list of all budgets
+ * known to the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @return QValueList of all MyMoneyBudget objects.
+ */
+ virtual const QValueList<MyMoneyBudget> budgetList( void ) const = 0;
+
+
+ /**
+ * This method adds a price entry to the price list.
+ */
+ virtual void addPrice(const MyMoneyPrice& price) = 0;
+
+ /**
+ * This method returns a list of all prices.
+ *
+ * @return MyMoneyPriceList of all MyMoneyPrice objects.
+ */
+ virtual const MyMoneyPriceList priceList(void) const = 0;
+
+ /**
+ * This method recalculates the balances of all accounts
+ * based on the transactions stored in the engine.
+ */
+ virtual void rebuildAccountBalances(void) = 0;
+
+};
+
+#endif
diff --git a/kmymoney2/mymoney/storage/imymoneystorage.cpp b/kmymoney2/mymoney/storage/imymoneystorage.cpp
new file mode 100644
index 0000000..dc67726
--- /dev/null
+++ b/kmymoney2/mymoney/storage/imymoneystorage.cpp
@@ -0,0 +1,38 @@
+/***************************************************************************
+ imymoneystorage.cpp - description
+ -------------------
+ begin : Sun May 5 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "imymoneystorage.h"
+
+bool MyMoneyFileBitArray::testBit(uint index) const
+{
+ if(index < size())
+ return QBitArray::testBit(index);
+ return false;
+}
+
+
+IMyMoneyStorage::IMyMoneyStorage()
+{
+}
+IMyMoneyStorage::~IMyMoneyStorage()
+{
+}
diff --git a/kmymoney2/mymoney/storage/imymoneystorage.h b/kmymoney2/mymoney/storage/imymoneystorage.h
new file mode 100644
index 0000000..a4e55dd
--- /dev/null
+++ b/kmymoney2/mymoney/storage/imymoneystorage.h
@@ -0,0 +1,886 @@
+/***************************************************************************
+ imymoneystorage.h - description
+ -------------------
+ begin : Sun May 5 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 IMYMONEYSTORAGE_H
+#define IMYMONEYSTORAGE_H
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstring.h>
+#include <qbitarray.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include <kmymoney/mymoneyutils.h>
+#include <kmymoney/mymoneyinstitution.h>
+#include <kmymoney/mymoneyaccount.h>
+#include <kmymoney/mymoneytransaction.h>
+#include <kmymoney/mymoneypayee.h>
+#include <kmymoney/mymoneyscheduled.h>
+#include <kmymoney/mymoneyobserver.h>
+#include <kmymoney/mymoneytransactionfilter.h>
+#include <kmymoney/mymoneysecurity.h>
+#include <kmymoney/mymoneyprice.h>
+#include <kmymoney/mymoneyreport.h>
+#include <kmymoney/mymoneybudget.h>
+
+/**
+ * @author Thomas Baumgart
+ *
+ * A simple replacement for QBitArray that does not bark if testBit()
+ * is called with an index out of bounds. It silently returns false
+ * in that case, otherwise calls the base classes implementation.
+ */
+class MyMoneyFileBitArray : public QBitArray
+{
+public:
+ MyMoneyFileBitArray() : QBitArray() {}
+ MyMoneyFileBitArray(int size) : QBitArray(size) {}
+ bool testBit(uint index) const;
+ bool operator[](int index) const { return testBit(index); }
+ bool at(uint index) const { return testBit(index); }
+};
+
+
+/**
+ * @author Thomas Baumgart
+ */
+
+/**
+ * The IMyMoneyStorage class describes the interface between the MyMoneyFile class
+ * and the real storage manager.
+ *
+ * @see MyMoneySeqAccessMgr
+ */
+class IMyMoneyStorage {
+public:
+
+ typedef enum {
+ RefCheckAccount = 0,
+ RefCheckInstitution,
+ RefCheckPayee,
+ RefCheckTransaction,
+ RefCheckReport,
+ RefCheckBudget,
+ RefCheckSchedule,
+ RefCheckSecurity,
+ RefCheckCurrency,
+ RefCheckPrice,
+ // insert new entries above this line
+ MaxRefCheckBits
+ } ReferenceCheckBits;
+
+ // definitions for the ID's of the standard accounts
+#define STD_ACC_LIABILITY "AStd::Liability"
+#define STD_ACC_ASSET "AStd::Asset"
+#define STD_ACC_EXPENSE "AStd::Expense"
+#define STD_ACC_INCOME "AStd::Income"
+#define STD_ACC_EQUITY "AStd::Equity"
+
+ IMyMoneyStorage();
+ virtual ~IMyMoneyStorage();
+
+ // general get functions
+ virtual const MyMoneyPayee user(void) const = 0;
+ virtual const QDate creationDate(void) const = 0;
+ virtual const QDate lastModificationDate(void) const = 0;
+ virtual unsigned int currentFixVersion(void) const = 0;
+ virtual unsigned int fileFixVersion(void) const = 0;
+
+ // general set functions
+ virtual void setUser(const MyMoneyPayee& user) = 0;
+ virtual void setFileFixVersion(const unsigned int v) = 0;
+
+ // methods provided by MyMoneyKeyValueContainer
+ virtual void setValue(const QString& key, const QString& value) = 0;
+ virtual const QString value(const QString& key) const = 0;
+ virtual void deletePair(const QString& key) = 0;
+
+ /**
+ * This method is used to duplicate an IMyMoneyStorage object and return
+ * a pointer to the newly created copy. The caller of this method is the
+ * new owner of the object and must destroy it.
+ */
+ virtual IMyMoneyStorage const * duplicate(void) = 0;
+
+ /**
+ * This method is used to create a new account
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param account MyMoneyAccount filled with data
+ */
+ virtual void addAccount(MyMoneyAccount& account) = 0;
+
+ /**
+ * This method is used to add one account as sub-ordinate to another
+ * (parent) account. The objects that are passed will be modified
+ * accordingly.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param parent parent account the account should be added to
+ * @param account the account to be added
+ */
+ virtual void addAccount(MyMoneyAccount& parent, MyMoneyAccount& account) = 0;
+
+ /**
+ * This method is used to create a new payee
+ *
+ * An exception will be thrown upon error conditions
+ *
+ * @param payee MyMoneyPayee reference to payee information
+ */
+ virtual void addPayee(MyMoneyPayee& payee) = 0;
+
+ /**
+ * This method is used to retrieve information about a payee
+ * An exception will be thrown upon error conditions.
+ *
+ * @param id QString reference to id of payee
+ *
+ * @return MyMoneyPayee object of payee
+ */
+ virtual const MyMoneyPayee payee(const QString& id) const = 0;
+
+ /**
+ * This method is used to retrieve the id to a corresponding
+ * name of a payee/receiver.
+ * An exception will be thrown upon error conditions.
+ *
+ * @param payee QString reference to name of payee
+ *
+ * @return MyMoneyPayee object of payee
+ */
+ virtual const MyMoneyPayee payeeByName(const QString& payee) const = 0;
+
+ /**
+ * This method is used to modify an existing payee
+ *
+ * An exception will be thrown upon error conditions
+ *
+ * @param payee MyMoneyPayee reference to payee information
+ */
+ virtual void modifyPayee(const MyMoneyPayee& payee) = 0;
+
+ /**
+ * This method is used to remove an existing payee
+ *
+ * An exception will be thrown upon error conditions
+ *
+ * @param payee MyMoneyPayee reference to payee information
+ */
+ virtual void removePayee(const MyMoneyPayee& payee) = 0;
+
+ /**
+ * This method returns a list of the payees
+ * inside a MyMoneyStorage object
+ *
+ * @return QValueList<MyMoneyPayee> containing the payee information
+ */
+ virtual const QValueList<MyMoneyPayee> payeeList(void) const = 0;
+
+ /**
+ * Returns the account addressed by it's id.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param id id of the account to locate.
+ * @return reference to MyMoneyAccount object. An exception is thrown
+ * if the id is unknown
+ */
+ virtual const MyMoneyAccount account(const QString& id) const = 0;
+
+ /**
+ * This method is used to check whether a given
+ * account id references one of the standard accounts or not.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param id account id
+ * @return true if account-id is one of the standards, false otherwise
+ */
+ virtual bool isStandardAccount(const QString& id) const = 0;
+
+ /**
+ * This method is used to set the name for the specified standard account
+ * within the storage area. An exception will be thrown, if an error
+ * occurs
+ *
+ * @param id QString reference to one of the standard accounts.
+ * @param name QString reference to the name to be set
+ *
+ */
+ virtual void setAccountName(const QString& id, const QString& name) = 0;
+
+ /**
+ * Adds an institution to the storage. A
+ * respective institution-ID will be generated within this record.
+ * The ID is stored as QString in the object passed as argument.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param institution The complete institution information in a
+ * MyMoneyInstitution object
+ */
+ virtual void addInstitution(MyMoneyInstitution& institution) = 0;
+
+ /**
+ * Adds a transaction to the file-global transaction pool. A respective
+ * transaction-ID will be generated within this record. The ID is stored
+ * QString with the object.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param transaction reference to the transaction
+ * @param skipAccountUpdate if set, the transaction lists of the accounts
+ * referenced in the splits are not updated. This is used for
+ * bulk loading a lot of transactions but not during normal operation
+ */
+ virtual void addTransaction(MyMoneyTransaction& transaction, const bool skipAccountUpdate = false) = 0;
+
+ /**
+ * This method is used to determince, if the account with the
+ * given ID is referenced by any split in m_transactionList.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param id id of the account to be checked for
+ * @return true if account is referenced, false otherwise
+ */
+ virtual bool hasActiveSplits(const QString& id) const = 0;
+
+ /**
+ * This method is used to return the actual balance of an account
+ * without it's sub-ordinate accounts. If a @p date is presented,
+ * the balance at the beginning of this date (not including any
+ * transaction on this date) is returned. Otherwise all recorded
+ * transactions are included in the balance.
+ *
+ * @param id id of the account in question
+ * @param date return balance for specific date
+ * @return balance of the account as MyMoneyMoney object
+ */
+ virtual const MyMoneyMoney balance(const QString& id, const QDate& date) const= 0;
+
+ /**
+ * This method is used to return the actual balance of an account
+ * including it's sub-ordinate accounts. If a @p date is presented,
+ * the balance at the beginning of this date (not including any
+ * transaction on this date) is returned. Otherwise all recorded
+ * transactions are included in the balance.
+ *
+ * @param id id of the account in question
+ * @param date return balance for specific date
+ * @return balance of the account as MyMoneyMoney object
+ */
+ virtual const MyMoneyMoney totalBalance(const QString& id, const QDate& date) const = 0;
+
+ /**
+ * Returns the institution of a given ID
+ *
+ * @param id id of the institution to locate
+ * @return MyMoneyInstitution object filled with data. If the institution
+ * could not be found, an exception will be thrown
+ */
+ virtual const MyMoneyInstitution institution(const QString& id) const = 0;
+
+ /**
+ * This method returns an indicator if the storage object has been
+ * changed after it has last been saved to permanent storage.
+ *
+ * @return true if changed, false if not
+ */
+ virtual bool dirty(void) const = 0;
+
+ /**
+ * This method can be used by an external object to force the
+ * storage object to be dirty. This is used e.g. when an upload
+ * to an external destination failed but the previous storage
+ * to a local disk was ok.
+ */
+ virtual void setDirty(void) = 0;
+
+ /**
+ * This method returns the number of accounts currently known to this storage
+ * in the range 0..MAXUINT
+ *
+ * @return number of accounts currently known inside a MyMoneyFile object
+ */
+ virtual unsigned int accountCount(void) const = 0;
+
+ /**
+ * This method returns a list of the institutions
+ * inside a MyMoneyStorage object
+ *
+ * @return QValueList<MyMoneyInstitution> containing the
+ * institution information
+ */
+ virtual const QValueList<MyMoneyInstitution> institutionList(void) const = 0;
+
+ /**
+ * Modifies an already existing account in the file global account pool.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param account reference to the new account information
+ * @param skipCheck allows to skip the builtin consistency checks
+ */
+ virtual void modifyAccount(const MyMoneyAccount& account, const bool skipCheck = false) = 0;
+
+ /**
+ * Modifies an already existing institution in the file global
+ * institution pool.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param institution The complete new institution information
+ */
+ virtual void modifyInstitution(const MyMoneyInstitution& institution) = 0;
+
+ /**
+ * This method is used to update a specific transaction in the
+ * transaction pool of the MyMoneyFile object
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param transaction reference to transaction to be changed
+ */
+ virtual void modifyTransaction(const MyMoneyTransaction& transaction) = 0;
+
+ /**
+ * This method re-parents an existing account
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param account MyMoneyAccount reference to account to be re-parented
+ * @param parent MyMoneyAccount reference to new parent account
+ */
+ virtual void reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent) = 0;
+
+ /**
+ * This method is used to remove a transaction from the transaction
+ * pool (journal).
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param transaction const reference to transaction to be deleted
+ */
+ virtual void removeTransaction(const MyMoneyTransaction& transaction) = 0;
+
+ /**
+ * This method returns the number of transactions currently known to file
+ * in the range 0..MAXUINT
+ *
+ * @param account QString reference to account id. If account is empty
+ + all transactions (the journal) will be counted. If account
+ * is not empty it returns the number of transactions
+ * that have splits in this account.
+ *
+ * @return number of transactions in journal/account
+ */
+ virtual unsigned int transactionCount(const QString& account = QString()) const = 0;
+
+ /**
+ * This method returns a QMap filled with the number of transactions
+ * per account. The account id serves as index into the map. If one
+ * needs to have all transactionCounts() for many accounts, this method
+ * is faster than calling transactionCount(const QString& account) many
+ * times.
+ *
+ * @return QMap with numbers of transactions per account
+ */
+ virtual const QMap<QString, unsigned long> transactionCountMap(void) const = 0;
+
+ /**
+ * This method is used to pull a list of transactions from the file
+ * global transaction pool. It returns all those transactions
+ * that match the filter passed as argument. If the filter is empty,
+ * the whole journal will be returned.
+ * The list returned is sorted according to the transactions posting date.
+ * If more than one transaction exists for the same date, the order among
+ * them is undefined.
+ *
+ * @param filter MyMoneyTransactionFilter object with the match criteria
+ *
+ * @return set of transactions in form of a QValueList<MyMoneyTransaction>
+ */
+ virtual const QValueList<MyMoneyTransaction> transactionList(MyMoneyTransactionFilter& filter) const = 0;
+
+ virtual void transactionList(QValueList<MyMoneyTransaction>& list, MyMoneyTransactionFilter& filter) const = 0;
+
+ virtual void transactionList(QValueList<QPair<MyMoneyTransaction, MyMoneySplit> >& list, MyMoneyTransactionFilter& filter) const = 0;
+
+ /**
+ * Deletes an existing account from the file global account pool
+ * This method only allows to remove accounts that are not
+ * referenced by any split. Use moveSplits() to move splits
+ * to another account. An exception is thrown in case of a
+ * problem.
+ *
+ * @param account reference to the account to be deleted.
+ */
+ virtual void removeAccount(const MyMoneyAccount& account) = 0;
+
+ /**
+ * Deletes an existing institution from the file global institution pool
+ * Also modifies the accounts that reference this institution as
+ * their institution.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param institution institution to be deleted.
+ */
+ virtual void removeInstitution(const MyMoneyInstitution& institution) = 0;
+
+ /**
+ * This method is used to extract a transaction from the file global
+ * transaction pool through an id. In case of an invalid id, an
+ * exception will be thrown.
+ *
+ * @param id id of transaction as QString.
+ * @return reference to the requested transaction
+ */
+ virtual const MyMoneyTransaction transaction(const QString& id) const = 0;
+
+ /**
+ * This method is used to extract a transaction from the file global
+ * transaction pool through an index into an account.
+ *
+ * @param account id of the account as QString
+ * @param idx number of transaction in this account
+ * @return reference to MyMoneyTransaction object
+ */
+ virtual const MyMoneyTransaction transaction(const QString& account, const int idx) const = 0;
+
+ /**
+ * This method returns the number of institutions currently known to file
+ * in the range 0..MAXUINT
+ *
+ * @return number of institutions known to file
+ */
+ virtual unsigned int institutionCount(void) const = 0;
+
+ /**
+ * This method returns a list of accounts inside the storage object.
+ *
+ * @param list reference to QValueList receiving the account objects
+ *
+ * @note The standard accounts will not be returned
+ */
+ virtual void accountList(QValueList<MyMoneyAccount>& list) const = 0;
+
+ /**
+ * This method is used to return the standard liability account
+ * @return MyMoneyAccount liability account(group)
+ */
+ virtual const MyMoneyAccount liability(void) const = 0;
+
+ /**
+ * This method is used to return the standard asset account
+ * @return MyMoneyAccount asset account(group)
+ */
+ virtual const MyMoneyAccount asset(void) const = 0;
+
+ /**
+ * This method is used to return the standard expense account
+ * @return MyMoneyAccount expense account(group)
+ */
+ virtual const MyMoneyAccount expense(void) const = 0;
+
+ /**
+ * This method is used to return the standard income account
+ * @return MyMoneyAccount income account(group)
+ */
+ virtual const MyMoneyAccount income(void) const = 0;
+
+ /**
+ * This method is used to return the standard equity account
+ * @return MyMoneyAccount equity account(group)
+ */
+ virtual const MyMoneyAccount equity(void) const = 0;
+
+ /**
+ * This method is used to create a new security object. The ID will be created
+ * automatically. The object passed with the parameter @p security is modified
+ * to contain the assigned id.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param security MyMoneySecurity filled with data
+ */
+ virtual void addSecurity(MyMoneySecurity& security) = 0;
+
+ /**
+ * This method is used to modify an existing MyMoneySecurity
+ * object.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param security reference to the MyMoneySecurity object to be updated
+ */
+ virtual void modifySecurity(const MyMoneySecurity& security) = 0;
+
+ /**
+ * This method is used to remove an existing MyMoneySecurity object
+ * from the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param security reference to the MyMoneySecurity object to be removed
+ */
+ virtual void removeSecurity(const MyMoneySecurity& security) = 0;
+
+ /**
+ * This method is used to retrieve a single MyMoneySecurity object.
+ * The id of the object must be supplied in the parameter @p id.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param id QString containing the id of the MyMoneySecurity object
+ * @return MyMoneySecurity object
+ */
+ virtual const MyMoneySecurity security(const QString& id) const = 0;
+
+ /**
+ * This method returns a list of the security objects
+ * inside a MyMoneyStorage object
+ *
+ * @return QValueList<MyMoneySecurity> containing objects
+ */
+ virtual const QValueList<MyMoneySecurity> securityList(void) const = 0;
+
+ virtual void addPrice(const MyMoneyPrice& price) = 0;
+ virtual void removePrice(const MyMoneyPrice& price) = 0;
+ virtual const MyMoneyPrice price(const QString& fromId, const QString& toId, const QDate& date, const bool exactDate) const = 0;
+
+ /**
+ * This method returns a list of all prices.
+ *
+ * @return MyMoneyPriceList of all MyMoneyPrice objects.
+ */
+ virtual const MyMoneyPriceList priceList(void) const = 0;
+
+ /**
+ * This method is used to add a scheduled transaction to the engine.
+ * It must be sure, that the id of the object is not filled. When the
+ * method returns to the caller, the id will be filled with the
+ * newly created object id value.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param sched reference to the MyMoneySchedule object
+ */
+ virtual void addSchedule(MyMoneySchedule& sched) = 0;
+
+ /**
+ * This method is used to modify an existing MyMoneySchedule
+ * object. Therefor, the id attribute of the object must be set.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param sched const reference to the MyMoneySchedule object to be updated
+ */
+ virtual void modifySchedule(const MyMoneySchedule& sched) = 0;
+
+ /**
+ * This method is used to remove an existing MyMoneySchedule object
+ * from the engine. The id attribute of the object must be set.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param sched const reference to the MyMoneySchedule object to be updated
+ */
+ virtual void removeSchedule(const MyMoneySchedule& sched) = 0;
+
+ /**
+ * This method is used to retrieve a single MyMoneySchedule object.
+ * The id of the object must be supplied in the parameter @p id.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param id QString containing the id of the MyMoneySchedule object
+ * @return MyMoneySchedule object
+ */
+ virtual const MyMoneySchedule schedule(const QString& id) const = 0;
+
+ /**
+ * This method is used to extract a list of scheduled transactions
+ * according to the filter criteria passed as arguments.
+ *
+ * @param accountId only search for scheduled transactions that reference
+ * accound @p accountId. If accountId is the empty string,
+ * this filter is off. Default is @p QString().
+ * @param type only schedules of type @p type are searched for.
+ * See MyMoneySchedule::typeE for details.
+ * Default is MyMoneySchedule::TYPE_ANY
+ * @param occurence only schedules of occurence type @p occurance are searched for.
+ * See MyMoneySchedule::occurenceE for details.
+ * Default is MyMoneySchedule::OCCUR_ANY
+ * @param paymentType only schedules of payment method @p paymentType
+ * are searched for.
+ * See MyMoneySchedule::paymentTypeE for details.
+ * Default is MyMoneySchedule::STYPE_ANY
+ * @param startDate only schedules with payment dates after @p startDate
+ * are searched for. Default is all dates (QDate()).
+ * @param endDate only schedules with payment dates ending prior to @p endDate
+ * are searched for. Default is all dates (QDate()).
+ * @param overdue if true, only those schedules that are overdue are
+ * searched for. Default is false (all schedules will be returned).
+ *
+ * @return const QValueList<MyMoneySchedule> list of schedule objects.
+ */
+ virtual const QValueList<MyMoneySchedule> scheduleList(const QString& accountId = QString(),
+ const MyMoneySchedule::typeE type = MyMoneySchedule::TYPE_ANY,
+ const MyMoneySchedule::occurenceE occurence = MyMoneySchedule::OCCUR_ANY,
+ const MyMoneySchedule::paymentTypeE paymentType = MyMoneySchedule::STYPE_ANY,
+ const QDate& startDate = QDate(),
+ const QDate& endDate = QDate(),
+ const bool overdue = false) const = 0;
+
+ virtual const QValueList<MyMoneySchedule> scheduleListEx( int scheduleTypes,
+ int scheduleOcurrences,
+ int schedulePaymentTypes,
+ QDate startDate,
+ const QStringList& accounts=QStringList()) const = 0;
+
+ /**
+ * This method is used to add a new currency object to the engine.
+ * The ID of the object is the trading symbol, so there is no need for an additional
+ * ID since the symbol is guaranteed to be unique.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param currency reference to the MyMoneySecurity object
+ */
+ virtual void addCurrency(const MyMoneySecurity& currency) = 0;
+
+ /**
+ * This method is used to modify an existing MyMoneySecurity
+ * object.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param currency reference to the MyMoneyCurrency object
+ */
+ virtual void modifyCurrency(const MyMoneySecurity& currency) = 0;
+
+ /**
+ * This method is used to remove an existing MyMoneySecurity object
+ * from the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param currency reference to the MyMoneySecurity object
+ */
+ virtual void removeCurrency(const MyMoneySecurity& currency) = 0;
+
+ /**
+ * This method is used to retrieve a single MyMoneySecurity object.
+ * The id of the object must be supplied in the parameter @p id.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param id QString containing the id of the MyMoneySecurity object
+ * @return MyMoneyCurrency object
+ */
+ virtual const MyMoneySecurity currency(const QString& id) const = 0;
+
+ /**
+ * This method is used to retrieve the list of all currencies
+ * known to the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @return QValueList of all MyMoneySecurity objects representing a currency.
+ */
+ virtual const QValueList<MyMoneySecurity> currencyList(void) const = 0;
+
+ /**
+ * This method is used to retrieve the list of all reports
+ * known to the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @return QValueList of all MyMoneyReport objects.
+ */
+ virtual const QValueList<MyMoneyReport> reportList( void ) const = 0;
+
+ /**
+ * This method is used to add a new report to the engine.
+ * It must be sure, that the id of the object is not filled. When the
+ * method returns to the caller, the id will be filled with the
+ * newly created object id value.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param report reference to the MyMoneyReport object
+ */
+ virtual void addReport( MyMoneyReport& report ) = 0;
+
+ /**
+ * This method is used to modify an existing MyMoneyReport
+ * object. Therefor, the id attribute of the object must be set.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param report const reference to the MyMoneyReport object to be updated
+ */
+ virtual void modifyReport( const MyMoneyReport& report ) = 0;
+
+ /**
+ * This method returns the number of reports currently known to file
+ * in the range 0..MAXUINT
+ *
+ * @return number of reports known to file
+ */
+ virtual unsigned countReports( void ) const = 0;
+
+ /**
+ * This method is used to retrieve a single MyMoneyReport object.
+ * The id of the object must be supplied in the parameter @p id.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param id QString containing the id of the MyMoneyReport object
+ * @return MyMoneyReport object
+ */
+ virtual const MyMoneyReport report( const QString& id ) const = 0;
+
+ /**
+ * This method is used to remove an existing MyMoneyReport object
+ * from the engine. The id attribute of the object must be set.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param report const reference to the MyMoneyReport object to be updated
+ */
+ virtual void removeReport(const MyMoneyReport& report) = 0;
+
+ /**
+ * This method is used to retrieve the list of all budgets
+ * known to the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @return QValueList of all MyMoneyBudget objects.
+ */
+ virtual const QValueList<MyMoneyBudget> budgetList( void ) const = 0;
+
+ /**
+ * This method is used to add a new budget to the engine.
+ * It must be sure, that the id of the object is not filled. When the
+ * method returns to the caller, the id will be filled with the
+ * newly created object id value.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param budget reference to the MyMoneyBudget object
+ */
+ virtual void addBudget( MyMoneyBudget& budget ) = 0;
+
+ /**
+ * This method is used to retrieve the id to a corresponding
+ * name of a budget
+ * An exception will be thrown upon error conditions.
+ *
+ * @param budget QString reference to name of budget
+ *
+ * @return MyMoneyBudget object of budget
+ */
+ virtual const MyMoneyBudget budgetByName(const QString& budget) const = 0;
+
+ /**
+ * This method is used to modify an existing MyMoneyBudget
+ * object. Therefor, the id attribute of the object must be set.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param budget const reference to the MyMoneyBudget object to be updated
+ */
+ virtual void modifyBudget( const MyMoneyBudget& budget ) = 0;
+
+ /**
+ * This method returns the number of budgets currently known to file
+ * in the range 0..MAXUINT
+ *
+ * @return number of budgets known to file
+ */
+ virtual unsigned countBudgets( void ) const = 0;
+
+ /**
+ * This method is used to retrieve a single MyMoneyBudget object.
+ * The id of the object must be supplied in the parameter @p id.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param id QString containing the id of the MyMoneyBudget object
+ * @return MyMoneyBudget object
+ */
+ virtual MyMoneyBudget budget( const QString& id ) const = 0;
+
+ /**
+ * This method is used to remove an existing MyMoneyBudget object
+ * from the engine. The id attribute of the object must be set.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param budget const reference to the MyMoneyBudget object to be updated
+ */
+ virtual void removeBudget(const MyMoneyBudget& budget) = 0;
+
+
+
+ /**
+ * Clear all internal caches (used internally for performance measurements)
+ */
+ virtual void clearCache(void) = 0;
+
+ /**
+ * This method checks, if the given @p object is referenced
+ * by another engine object.
+ *
+ * @param obj const reference to object to be checked
+ * @param skipCheck MyMoneyFileBitArray with ReferenceCheckBits set for which
+ * the check should be skipped
+ *
+ * @retval false @p object is not referenced
+ * @retval true @p institution is referenced
+ */
+ virtual bool isReferenced(const MyMoneyObject& obj, const MyMoneyFileBitArray& skipCheck = MyMoneyFileBitArray()) const = 0;
+
+ /**
+ * This method is provided to allow closing of the database before logoff
+ */
+ virtual void close(void) = 0;
+
+ /**
+ * These methods have to be provided to allow transaction safe data handling.
+ */
+ virtual void startTransaction(void) = 0;
+ virtual bool commitTransaction(void) = 0;
+ virtual void rollbackTransaction(void) = 0;
+};
+
+#endif
diff --git a/kmymoney2/mymoney/storage/imymoneystorageformat.cpp b/kmymoney2/mymoney/storage/imymoneystorageformat.cpp
new file mode 100644
index 0000000..bc3bbb2
--- /dev/null
+++ b/kmymoney2/mymoney/storage/imymoneystorageformat.cpp
@@ -0,0 +1,31 @@
+/***************************************************************************
+ imymoneystorageformat.cpp - description
+ -------------------
+ begin : Sun Oct 27 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "imymoneystorageformat.h"
+
+IMyMoneyStorageFormat::IMyMoneyStorageFormat()
+{
+}
+
+IMyMoneyStorageFormat::~IMyMoneyStorageFormat()
+{
+}
diff --git a/kmymoney2/mymoney/storage/imymoneystorageformat.h b/kmymoney2/mymoney/storage/imymoneystorageformat.h
new file mode 100644
index 0000000..b045898
--- /dev/null
+++ b/kmymoney2/mymoney/storage/imymoneystorageformat.h
@@ -0,0 +1,75 @@
+/***************************************************************************
+ imymoneystorageformat.h - description
+ -------------------
+ begin : Sun Oct 27 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 IMYMONEYSTORAGEFORMAT_H
+#define IMYMONEYSTORAGEFORMAT_H
+
+
+/**
+ * @author Kevin Tambascio (ktambascio@yahoo.com)
+ */
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+class QString;
+class QIODevice;
+class QProgressDialog;
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+class IMyMoneySerialize;
+
+
+class IMyMoneyStorageFormat
+{
+public:
+ IMyMoneyStorageFormat();
+ virtual ~IMyMoneyStorageFormat();
+
+ enum fileVersionDirectionType {
+ Reading = 0, /**< version of file to be read */
+ Writing = 1 /**< version to be used when writing a file */
+ };
+
+ virtual void readFile(QIODevice* qf, IMyMoneySerialize* storage) = 0;
+ // virtual void readStream(QDataStream& s, IMyMoneySerialize* storage) = 0;
+
+ virtual void writeFile(QIODevice* qf, IMyMoneySerialize* storage) = 0;
+ //virtual void writeStream(QDataStream& s, IMyMoneySerialize* storage) = 0;
+
+ virtual void setProgressCallback(void(*callback)(int, int, const QString&)) = 0;
+ /**
+ * This member is used to store the file version information
+ * obtained while reading a file.
+ */
+ static unsigned int fileVersionRead;
+
+ /**
+ * This member is used to store the file version information
+ * to be used when writing a file.
+ */
+ static unsigned int fileVersionWrite;
+};
+
+#endif
diff --git a/kmymoney2/mymoney/storage/mymoneydatabasemgr.cpp b/kmymoney2/mymoney/storage/mymoneydatabasemgr.cpp
new file mode 100644
index 0000000..e845094
--- /dev/null
+++ b/kmymoney2/mymoney/storage/mymoneydatabasemgr.cpp
@@ -0,0 +1,1880 @@
+/***************************************************************************
+ mymoneydatabasemgr.cpp
+ -------------------
+ begin : June 5 2007
+ copyright : (C) 2007 by Fernando Vilas
+ email : Fernando Vilas <fvilas@iname.com>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 <typeinfo>
+#include <algorithm>
+
+#include "mymoneydatabasemgr.h"
+#include "../mymoneytransactionfilter.h"
+#include "../mymoneycategory.h"
+
+#define TRY try {
+#define CATCH } catch (MyMoneyException *e) {
+#define PASS } catch (MyMoneyException *e) { throw; }
+
+MyMoneyDatabaseMgr::MyMoneyDatabaseMgr() :
+m_creationDate (QDate::currentDate ()),
+m_lastModificationDate (QDate::currentDate ()),
+m_sql (0)
+{ }
+
+MyMoneyDatabaseMgr::~MyMoneyDatabaseMgr()
+{ }
+
+ // general get functions
+const MyMoneyPayee MyMoneyDatabaseMgr::user(void) const
+{ return m_user; }
+
+const QDate MyMoneyDatabaseMgr::creationDate(void) const
+{ return m_creationDate; }
+
+const QDate MyMoneyDatabaseMgr::lastModificationDate(void) const
+{ return m_lastModificationDate; }
+
+unsigned int MyMoneyDatabaseMgr::currentFixVersion(void) const
+{ return CURRENT_FIX_VERSION; }
+
+unsigned int MyMoneyDatabaseMgr::fileFixVersion(void) const
+{ return m_fileFixVersion; }
+
+ // general set functions
+void MyMoneyDatabaseMgr::setUser(const MyMoneyPayee& user)
+{
+ m_user = user;
+ if (m_sql != 0) m_sql->modifyUserInfo(user);
+}
+
+void MyMoneyDatabaseMgr::setFileFixVersion(const unsigned int v)
+{ m_fileFixVersion = v; }
+
+ // methods provided by MyMoneyKeyValueContainer
+const QString MyMoneyDatabaseMgr::value(const QString& key) const
+{
+ return MyMoneyKeyValueContainer::value(key);
+}
+
+void MyMoneyDatabaseMgr::setValue(const QString& key, const QString& val)
+{
+ MyMoneyKeyValueContainer::setValue(key, val);
+}
+
+void MyMoneyDatabaseMgr::deletePair(const QString& key)
+{
+ MyMoneyKeyValueContainer::deletePair(key);
+}
+
+const QMap<QString, QString> MyMoneyDatabaseMgr::pairs(void) const
+{
+ return MyMoneyKeyValueContainer::pairs();
+}
+
+void MyMoneyDatabaseMgr::setPairs(const QMap<QString, QString>& list)
+{
+ MyMoneyKeyValueContainer::setPairs(list);
+}
+
+MyMoneyDatabaseMgr const * MyMoneyDatabaseMgr::duplicate(void)
+{
+ MyMoneyDatabaseMgr* that = new MyMoneyDatabaseMgr();
+ *that = *this;
+ return that;
+}
+
+void MyMoneyDatabaseMgr::addAccount(MyMoneyAccount& account)
+{
+ if (m_sql) {
+ // create the account.
+ MyMoneyAccount newAccount(nextAccountID(), account);
+
+ m_sql->addAccount(newAccount);
+ account = newAccount;
+ }
+}
+
+void MyMoneyDatabaseMgr::addAccount(MyMoneyAccount& parent, MyMoneyAccount& account)
+{
+ QMap<QString, MyMoneyAccount> accountList;
+ QStringList accountIdList;
+ QMap<QString, MyMoneyAccount>::ConstIterator theParent;
+ QMap<QString, MyMoneyAccount>::ConstIterator theChild;
+
+ accountIdList << parent.id() << account.id();
+ startTransaction();
+ accountList = m_sql->fetchAccounts(accountIdList, true);
+
+ theParent = accountList.find(parent.id());
+ if(theParent == accountList.end()) {
+ QString msg = "Unknown parent account '";
+ msg += parent.id() + "'";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+
+ theChild = accountList.find(account.id());
+ if(theChild == accountList.end()) {
+ QString msg = "Unknown child account '";
+ msg += account.id() + "'";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+
+ MyMoneyAccount acc = *theParent;
+ acc.addAccountId(account.id());
+ parent = acc;
+
+ acc = *theChild;
+ acc.setParentAccountId(parent.id());
+ account = acc;
+
+//FIXME: MyMoneyBalanceCacheItem balance;
+//FIXME: m_balanceCache[account.id()] = balance;
+
+ m_sql->modifyAccount(parent);
+ m_sql->modifyAccount(account);
+ commitTransaction();
+}
+
+void MyMoneyDatabaseMgr::addPayee(MyMoneyPayee& payee)
+{
+ if (m_sql) {
+ // create the payee
+ MyMoneyPayee newPayee(nextPayeeID(), payee);
+
+ m_sql->addPayee(newPayee);
+ payee = newPayee;
+ }
+}
+
+const MyMoneyPayee MyMoneyDatabaseMgr::payee(const QString& id) const
+{
+ QMap<QString, MyMoneyPayee>::ConstIterator it;
+ QMap<QString, MyMoneyPayee> payeeList = m_sql->fetchPayees(QString(id));
+ it = payeeList.find(id);
+ if(it == payeeList.end())
+ throw new MYMONEYEXCEPTION("Unknown payee '" + id + "'");
+
+ return *it;
+}
+
+const MyMoneyPayee MyMoneyDatabaseMgr::payeeByName(const QString& payee) const
+{
+ if(payee.isEmpty())
+ return MyMoneyPayee::null;
+
+ QMap<QString, MyMoneyPayee> payeeList;
+
+ TRY
+ payeeList = m_sql->fetchPayees();
+ PASS
+
+ QMap<QString, MyMoneyPayee>::ConstIterator it_p;
+
+ for(it_p = payeeList.begin(); it_p != payeeList.end(); ++it_p) {
+ if((*it_p).name() == payee) {
+ return *it_p;
+ }
+ }
+
+ throw new MYMONEYEXCEPTION("Unknown payee '" + payee + "'");
+}
+
+void MyMoneyDatabaseMgr::modifyPayee(const MyMoneyPayee& payee)
+{
+ QMap<QString, MyMoneyPayee> payeeList = m_sql->fetchPayees(QString(payee.id()), true);
+ QMap<QString, MyMoneyPayee>::ConstIterator it;
+
+ it = payeeList.find(payee.id());
+ if(it == payeeList.end()) {
+ QString msg = "Unknown payee '" + payee.id() + "'";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+
+ m_sql->modifyPayee(payee);
+}
+
+void MyMoneyDatabaseMgr::removePayee(const MyMoneyPayee& payee)
+{
+ QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
+ QMap<QString, MyMoneySchedule>::ConstIterator it_s;
+ QMap<QString, MyMoneyPayee> payeeList = m_sql->fetchPayees(QString(payee.id()));
+ QMap<QString, MyMoneyPayee>::ConstIterator it_p;
+
+ it_p = payeeList.find(payee.id());
+ if(it_p == payeeList.end()) {
+ QString msg = "Unknown payee '" + payee.id() + "'";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+
+ // scan all transactions to check if the payee is still referenced
+ QMap<QString, MyMoneyTransaction> transactionList = m_sql->fetchTransactions(); // make sure they're all here
+ for(it_t = transactionList.begin(); it_t != transactionList.end(); ++it_t) {
+ if((*it_t).hasReferenceTo(payee.id())) {
+ throw new MYMONEYEXCEPTION(QString("Cannot remove payee that is still referenced to a %1").arg("transaction"));
+ }
+ }
+
+ // check referential integrity in schedules
+ QMap<QString, MyMoneySchedule> scheduleList = m_sql->fetchSchedules(); // make sure they're all here
+ for(it_s = scheduleList.begin(); it_s != scheduleList.end(); ++it_s) {
+ if((*it_s).hasReferenceTo(payee.id())) {
+ throw new MYMONEYEXCEPTION(QString("Cannot remove payee that is still referenced to a %1").arg("schedule"));
+ }
+ }
+ // remove any reference to report and/or budget
+ removeReferences(payee.id());
+
+ m_sql->removePayee(payee);
+}
+
+const QValueList<MyMoneyPayee> MyMoneyDatabaseMgr::payeeList(void) const
+{
+ if (m_sql)
+ return m_sql->fetchPayees().values();
+ else
+ return QValueList<MyMoneyPayee> ();
+}
+
+const MyMoneyAccount MyMoneyDatabaseMgr::account(const QString& id) const
+{
+ if (m_sql)
+ {
+ QMap <QString, MyMoneyAccount> accountList = m_sql->fetchAccounts(QString(id));
+ QMap <QString, MyMoneyAccount>::ConstIterator pos = accountList.find(id);
+
+ // locate the account and if present, return it's data
+ if(pos != accountList.end())
+ return *pos;
+ }
+
+ // throw an exception, if it does not exist
+ QString msg = "Unknown account id '" + id + "'";
+ throw new MYMONEYEXCEPTION(msg);
+}
+
+bool MyMoneyDatabaseMgr::isStandardAccount(const QString& id) const
+{
+ return id == STD_ACC_LIABILITY
+ || id == STD_ACC_ASSET
+ || id == STD_ACC_EXPENSE
+ || id == STD_ACC_INCOME
+ || id == STD_ACC_EQUITY;
+}
+
+void MyMoneyDatabaseMgr::setAccountName(const QString& id, const QString& name)
+{
+ if(!isStandardAccount(id))
+ throw new MYMONEYEXCEPTION("Only standard accounts can be modified using setAccountName()");
+
+ if (m_sql) {
+ startTransaction();
+ MyMoneyAccount acc = m_sql->fetchAccounts(QString(id), true) [id];
+ acc.setName(name);
+ m_sql->modifyAccount(acc);
+ commitTransaction();
+ }
+}
+
+void MyMoneyDatabaseMgr::addInstitution(MyMoneyInstitution& institution)
+{
+ if (m_sql) {
+ MyMoneyInstitution newInstitution(nextInstitutionID(), institution);
+
+ // mark file as changed
+ m_sql->addInstitution (newInstitution);
+
+ // return new data
+ institution = newInstitution;
+ }
+}
+
+const QString MyMoneyDatabaseMgr::nextPayeeID(void)
+{
+ QString id;
+ if (m_sql) {
+ id.setNum(ulong(m_sql->incrementPayeeId()));
+ id = "P" + id.rightJustify(PAYEE_ID_SIZE, '0');
+ }
+ return id;
+}
+
+const QString MyMoneyDatabaseMgr::nextInstitutionID(void)
+{
+ QString id;
+ if (m_sql) {
+ id.setNum(ulong(m_sql->incrementInstitutionId()));
+ id = "I" + id.rightJustify(INSTITUTION_ID_SIZE, '0');
+ }
+ return id;
+}
+
+const QString MyMoneyDatabaseMgr::nextAccountID(void)
+{
+ QString id;
+ if (m_sql) {
+ id.setNum(ulong(m_sql->incrementAccountId()));
+ id = "A" + id.rightJustify(ACCOUNT_ID_SIZE, '0');
+ }
+ return id;
+}
+
+const QString MyMoneyDatabaseMgr::nextBudgetID(void)
+{
+ QString id;
+ if (m_sql) {
+ id.setNum(ulong(m_sql->incrementBudgetId()));
+ id = "B" + id.rightJustify(BUDGET_ID_SIZE, '0');
+ }
+ return id;
+}
+
+const QString MyMoneyDatabaseMgr::nextReportID(void)
+{
+ QString id;
+ if (m_sql) {
+ id.setNum(ulong(m_sql->incrementReportId()));
+ id = "R" + id.rightJustify(REPORT_ID_SIZE, '0');
+ }
+ return id;
+}
+
+const QString MyMoneyDatabaseMgr::nextTransactionID(void)
+{
+ QString id;
+ if (m_sql) {
+ id.setNum(ulong(m_sql->incrementTransactionId()));
+ id = "T" + id.rightJustify(TRANSACTION_ID_SIZE, '0');
+ }
+ return id;
+}
+
+const QString MyMoneyDatabaseMgr::nextScheduleID(void)
+{
+ QString id;
+ if (m_sql) {
+ id.setNum(ulong(m_sql->incrementScheduleId()));
+ id = "SCH" + id.rightJustify(SCHEDULE_ID_SIZE, '0');
+ }
+ return id;
+}
+
+const QString MyMoneyDatabaseMgr::nextSecurityID(void)
+{
+ QString id;
+ if (m_sql) {
+ id.setNum(ulong(m_sql->incrementSecurityId()));
+ id = "E" + id.rightJustify(SECURITY_ID_SIZE, '0');
+ }
+ return id;
+}
+
+void MyMoneyDatabaseMgr::addTransaction(MyMoneyTransaction& transaction, const bool skipAccountUpdate)
+{
+ // perform some checks to see that the transaction stuff is OK. For
+ // now we assume that
+ // * no ids are assigned
+ // * the date valid (must not be empty)
+ // * the referenced accounts in the splits exist
+
+ // first perform all the checks
+ if(!transaction.id().isEmpty())
+ throw new MYMONEYEXCEPTION("transaction already contains an id");
+ if(!transaction.postDate().isValid())
+ throw new MYMONEYEXCEPTION("invalid post date");
+
+ // now check the splits
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+ for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
+ // the following lines will throw an exception if the
+ // account or payee do not exist
+ account((*it_s).accountId());
+ if(!(*it_s).payeeId().isEmpty())
+ payee((*it_s).payeeId());
+ }
+
+ MyMoneyTransaction newTransaction(nextTransactionID(), transaction);
+ QString key = newTransaction.uniqueSortKey();
+
+ m_sql->addTransaction(newTransaction);
+
+ transaction = newTransaction;
+
+ // adjust the balance of all affected accounts
+ for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
+ MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId());
+ acc.adjustBalance((*it_s));
+ if(!skipAccountUpdate) {
+ acc.touch();
+//FIXME: invalidateBalanceCache(acc.id());
+ }
+ m_sql->modifyAccount(acc);
+ }
+}
+
+bool MyMoneyDatabaseMgr::hasActiveSplits(const QString& id) const
+{
+ QMap<QString, MyMoneyTransaction>::ConstIterator it;
+
+ MyMoneyTransactionFilter f(id);
+ QMap<QString, MyMoneyTransaction> transactionList = m_sql->fetchTransactions(f);
+
+ for(it = transactionList.begin(); it != transactionList.end(); ++it) {
+ if((*it).accountReferenced(id)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+ /**
+ * This method is used to return the actual balance of an account
+ * without it's sub-ordinate accounts. If a @p date is presented,
+ * the balance at the beginning of this date (not including any
+ * transaction on this date) is returned. Otherwise all recorded
+ * transactions are included in the balance.
+ *
+ * @param id id of the account in question
+ * @param date return balance for specific date
+ * @return balance of the account as MyMoneyMoney object
+ */
+//const MyMoneyMoney MyMoneyDatabaseMgr::balance(const QString& id, const QDate& date);
+
+const MyMoneyMoney MyMoneyDatabaseMgr::totalBalance(const QString& id, const QDate& date) const
+{
+ QStringList accounts;
+ QStringList::ConstIterator it_a;
+
+ MyMoneyMoney result; //(balance(id, date));
+
+ accounts = MyMoneyFile::instance()->account(id).accountList();
+ for (it_a = accounts.begin(); it_a != accounts.end(); ++it_a) {
+ accounts += MyMoneyFile::instance()->account(*it_a).accountList();
+ }
+ std::list <QString> tempList (accounts.begin(), accounts.end());
+ tempList.sort();;
+ tempList.unique();
+
+ accounts = QStringList(tempList);
+
+ QMap<QString, MyMoneyMoney> balanceMap = m_sql->fetchBalance(accounts, date);
+ for (QMap<QString, MyMoneyMoney>::ConstIterator it_b = balanceMap.begin(); it_b != balanceMap.end(); ++it_b) {
+ result += it_b.data();
+ }
+
+ return result;
+}
+
+const MyMoneyInstitution MyMoneyDatabaseMgr::institution(const QString& id) const
+{
+ QMap<QString, MyMoneyInstitution>::ConstIterator pos;
+ QMap<QString, MyMoneyInstitution> institutionList = m_sql->fetchInstitutions(QString(id));
+
+ pos = institutionList.find(id);
+ if(pos != institutionList.end())
+ return *pos;
+ throw new MYMONEYEXCEPTION("unknown institution");
+}
+
+bool MyMoneyDatabaseMgr::dirty(void) const
+{ return false; }
+
+void MyMoneyDatabaseMgr::setDirty(void)
+{}
+
+unsigned int MyMoneyDatabaseMgr::accountCount(void) const
+{
+ return m_sql->getRecCount("kmmAccounts");
+}
+
+const QValueList<MyMoneyInstitution> MyMoneyDatabaseMgr::institutionList(void) const
+{
+ if (m_sql) {
+ return m_sql->fetchInstitutions().values();
+ } else {
+ return QValueList<MyMoneyInstitution> ();
+ }
+}
+
+void MyMoneyDatabaseMgr::modifyAccount(const MyMoneyAccount& account, const bool skipCheck)
+{
+ QMap<QString, MyMoneyAccount>::ConstIterator pos;
+
+ // locate the account in the file global pool
+ startTransaction();
+ QMap<QString, MyMoneyAccount> accountList = m_sql->fetchAccounts (QString(account.id()), true);
+ pos = accountList.find(account.id());
+ if(pos != accountList.end()) {
+ // check if the new info is based on the old one.
+ // this is the case, when the file and the id
+ // as well as the type are equal.
+ if(((*pos).parentAccountId() == account.parentAccountId()
+ && (*pos).accountType() == account.accountType())
+ || skipCheck == true) {
+ // make sure that all the referenced objects exist
+ if(!account.institutionId().isEmpty())
+ institution(account.institutionId());
+
+ QValueList<QString>::ConstIterator it_a;
+ for(it_a = account.accountList().begin(); it_a != account.accountList().end(); ++it_a) {
+ this->account(*it_a);
+ }
+
+ // update information in account list
+ //m_accountList.modify(account.id(), account);
+
+ // invalidate cached balance
+//FIXME: invalidateBalanceCache(account.id());
+
+ // mark file as changed
+ m_sql->modifyAccount(account);
+ commitTransaction();
+ } else {
+ rollbackTransaction();
+ throw new MYMONEYEXCEPTION("Invalid information for update");
+ }
+
+ } else {
+ rollbackTransaction();
+ throw new MYMONEYEXCEPTION("Unknown account id");
+ }
+}
+
+void MyMoneyDatabaseMgr::modifyInstitution(const MyMoneyInstitution& institution)
+{
+ QMap<QString, MyMoneyInstitution> institutionList = m_sql->fetchInstitutions(QString(institution.id()));
+ QMap<QString, MyMoneyInstitution>::ConstIterator pos;
+
+ // locate the institution in the file global pool
+ pos = institutionList.find(institution.id());
+ if(pos != institutionList.end()) {
+ m_sql->modifyInstitution(institution);
+ } else
+ throw new MYMONEYEXCEPTION("unknown institution");
+}
+
+ /**
+ * This method is used to update a specific transaction in the
+ * transaction pool of the MyMoneyFile object
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param transaction reference to transaction to be changed
+ */
+void MyMoneyDatabaseMgr::modifyTransaction(const MyMoneyTransaction& transaction)
+{
+ QMap<QString, bool> modifiedAccounts;
+
+ // perform some checks to see that the transaction stuff is OK. For
+ // now we assume that
+ // * ids are assigned
+ // * the pointer to the MyMoneyFile object is not 0
+ // * the date valid (must not be empty)
+ // * the splits must have valid account ids
+
+ // first perform all the checks
+ if(transaction.id().isEmpty()
+// || transaction.file() != this
+ || !transaction.postDate().isValid())
+ throw new MYMONEYEXCEPTION("invalid transaction to be modified");
+
+ // now check the splits
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+ for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
+ // the following lines will throw an exception if the
+ // account or payee do not exist
+ MyMoneyFile::instance()->account((*it_s).accountId());
+ if(!(*it_s).payeeId().isEmpty())
+ MyMoneyFile::instance()->payee((*it_s).payeeId());
+ }
+
+ // new data seems to be ok. find old version of transaction
+ // in our pool. Throw exception if unknown.
+// if(!m_transactionKeys.contains(transaction.id()))
+// throw new MYMONEYEXCEPTION("invalid transaction id");
+
+// QString oldKey = m_transactionKeys[transaction.id()];
+ QMap <QString, MyMoneyTransaction> transactionList = m_sql->fetchTransactions("('" + QString(transaction.id()) + "')");
+// if(transactionList.size() != 1)
+// throw new MYMONEYEXCEPTION("invalid transaction key");
+
+ QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
+
+// it_t = transactionList.find(oldKey);
+ it_t = transactionList.begin();
+ if(it_t == transactionList.end())
+ throw new MYMONEYEXCEPTION("invalid transaction key");
+
+ // mark all accounts referenced in old and new transaction data
+ // as modified
+ QMap<QString, MyMoneyAccount> accountList = m_sql->fetchAccounts();
+ for(it_s = (*it_t).splits().begin(); it_s != (*it_t).splits().end(); ++it_s) {
+ MyMoneyAccount acc = accountList[(*it_s).accountId()];
+ acc.adjustBalance((*it_s), true);
+ acc.touch();
+//FIXME: invalidateBalanceCache(acc.id());
+ //m_accountList.modify(acc.id(), acc);
+ m_sql->modifyAccount(acc);
+ //modifiedAccounts[(*it_s).accountId()] = true;
+ }
+ for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
+ MyMoneyAccount acc = accountList[(*it_s).accountId()];
+ acc.adjustBalance((*it_s));
+ acc.touch();
+//FIXME: invalidateBalanceCache(acc.id());
+ //m_accountList.modify(acc.id(), acc);
+ m_sql->modifyAccount(acc);
+ //modifiedAccounts[(*it_s).accountId()] = true;
+ }
+
+ // remove old transaction from lists
+// m_sql->removeTransaction(oldKey);
+
+ // add new transaction to lists
+ // QString newKey = transaction.uniqueSortKey();
+// m_sql->insertTransaction(newKey, transaction);
+ //m_transactionKeys.modify(transaction.id(), newKey);
+
+ // mark file as changed
+ m_sql->modifyTransaction(transaction);
+}
+
+void MyMoneyDatabaseMgr::reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent)
+{
+ if(account.accountType() == MyMoneyAccount::Stock && parent.accountType() != MyMoneyAccount::Investment)
+ throw new MYMONEYEXCEPTION("Cannot move a stock acocunt into a non-investment account");
+
+ QStringList accountIdList;
+ QMap<QString, MyMoneyAccount>::ConstIterator oldParent;
+ QMap<QString, MyMoneyAccount>::ConstIterator newParent;
+ QMap<QString, MyMoneyAccount>::ConstIterator childAccount;
+
+ // verify that accounts exist. If one does not,
+ // an exception is thrown
+ accountIdList << account.id() << parent.id();
+ MyMoneyDatabaseMgr::account(account.id());
+ MyMoneyDatabaseMgr::account(parent.id());
+
+ if(!account.parentAccountId().isEmpty()) {
+ accountIdList << account.parentAccountId();
+ }
+
+ startTransaction();
+ QMap<QString, MyMoneyAccount> accountList = m_sql->fetchAccounts(accountIdList, true);
+
+ if(!account.parentAccountId().isEmpty()) {
+ MyMoneyDatabaseMgr::account(account.parentAccountId());
+ oldParent = accountList.find(account.parentAccountId());
+ }
+
+ newParent = accountList.find(parent.id());
+ childAccount = accountList.find(account.id());
+
+ MyMoneyAccount acc;
+ if(!account.parentAccountId().isEmpty()) {
+ acc = (*oldParent);
+ acc.removeAccountId(account.id());
+ m_sql->modifyAccount(acc);
+ }
+
+ parent = (*newParent);
+ parent.addAccountId(account.id());
+
+ account = (*childAccount);
+ account.setParentAccountId(parent.id());
+
+ m_sql->modifyAccount(parent);
+ m_sql->modifyAccount(account);
+ commitTransaction();
+}
+
+void MyMoneyDatabaseMgr::removeTransaction(const MyMoneyTransaction& transaction)
+{
+ QMap<QString, bool> modifiedAccounts;
+
+ // first perform all the checks
+ if(transaction.id().isEmpty())
+ throw new MYMONEYEXCEPTION("invalid transaction to be deleted");
+
+ QMap<QString, QString>::ConstIterator it_k;
+ QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
+
+// it_k = m_transactionKeys.find(transaction.id());
+// if(it_k == m_transactionKeys.end())
+// throw new MYMONEYEXCEPTION("invalid transaction to be deleted");
+
+ QMap <QString, MyMoneyTransaction> transactionList = m_sql->fetchTransactions("('" + QString(transaction.id()) + "')");
+// it_t = transactionList.find(*it_k);
+ it_t = transactionList.begin();
+ if(it_t == transactionList.end())
+ throw new MYMONEYEXCEPTION("invalid transaction key");
+
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+
+ // scan the splits and collect all accounts that need
+ // to be updated after the removal of this transaction
+ QMap<QString, MyMoneyAccount> accountList = m_sql->fetchAccounts();
+ for(it_s = (*it_t).splits().begin(); it_s != (*it_t).splits().end(); ++it_s) {
+ MyMoneyAccount acc = accountList[(*it_s).accountId()];
+// modifiedAccounts[(*it_s).accountId()] = true;
+ acc.adjustBalance((*it_s), true);
+ acc.touch();
+ m_sql->modifyAccount(acc);
+//FIXME: invalidateBalanceCache(acc.id());
+ }
+
+ // FIXME: check if any split is frozen and throw exception
+
+ // remove the transaction from the two lists
+ //m_transactionList.remove(*it_k);
+// m_transactionKeys.remove(transaction.id());
+
+ // mark file as changed
+ m_sql->removeTransaction(transaction);
+}
+
+unsigned int MyMoneyDatabaseMgr::transactionCount(const QString& account) const
+{ return (m_sql->transactionCount(account)); }
+
+const QMap<QString, unsigned long> MyMoneyDatabaseMgr::transactionCountMap(void) const
+{ return (m_sql->transactionCountMap()); }
+
+const QValueList<MyMoneyTransaction> MyMoneyDatabaseMgr::transactionList(MyMoneyTransactionFilter& filter) const
+{
+ QValueList<MyMoneyTransaction> list;
+ transactionList(list, filter);
+ return list;
+}
+
+void MyMoneyDatabaseMgr::transactionList(QValueList<MyMoneyTransaction>& list, MyMoneyTransactionFilter& filter) const
+{
+ list.clear();
+
+ TRY
+ if (m_sql) list = m_sql->fetchTransactions(filter).values();
+ PASS
+}
+
+void MyMoneyDatabaseMgr::transactionList(QValueList<QPair<MyMoneyTransaction, MyMoneySplit> >& list, MyMoneyTransactionFilter& filter) const
+{
+ list.clear();
+ MyMoneyMap<QString, MyMoneyTransaction> transactionList;
+ TRY
+ if (m_sql) transactionList = m_sql->fetchTransactions(filter);
+ PASS
+
+ QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
+ QMap<QString, MyMoneyTransaction>::ConstIterator txEnd = transactionList.end();
+
+ for(it_t = transactionList.begin(); it_t != txEnd; ++it_t) {
+ if(filter.match(*it_t)) {
+ QValueList<MyMoneySplit>::const_iterator it_s;
+ for(it_s = filter.matchingSplits().begin(); it_s != filter.matchingSplits().end(); ++it_s) {
+ list.append(qMakePair(*it_t, *it_s));
+ }
+ }
+ }
+}
+
+void MyMoneyDatabaseMgr::removeAccount(const MyMoneyAccount& account)
+{
+ MyMoneyAccount parent;
+
+ // check that the account and it's parent exist
+ // this will throw an exception if the id is unknown
+ MyMoneyDatabaseMgr::account(account.id());
+ parent = MyMoneyDatabaseMgr::account(account.parentAccountId());
+
+ // check that it's not one of the standard account groups
+ if(isStandardAccount(account.id()))
+ throw new MYMONEYEXCEPTION("Unable to remove the standard account groups");
+
+ if(hasActiveSplits(account.id())) {
+ throw new MYMONEYEXCEPTION("Unable to remove account with active splits");
+ }
+
+ // re-parent all sub-ordinate accounts to the parent of the account
+ // to be deleted. First round check that all accounts exist, second
+ // round do the re-parenting.
+ QStringList::ConstIterator it;
+ for(it = account.accountList().begin(); it != account.accountList().end(); ++it) {
+ MyMoneyDatabaseMgr::account(*it);
+ }
+
+ // if one of the accounts did not exist, an exception had been
+ // thrown and we would not make it until here.
+
+ QStringList accountIdList;
+ accountIdList << parent.id() << account.id();
+ startTransaction();
+ QMap<QString, MyMoneyAccount> accountList = m_sql->fetchAccounts(accountIdList, true);
+
+ QMap<QString, MyMoneyAccount>::ConstIterator it_a;
+ QMap<QString, MyMoneyAccount>::ConstIterator it_p;
+
+ // locate the account in the file global pool
+
+ it_a = accountList.find(account.id());
+ if(it_a == accountList.end())
+ throw new MYMONEYEXCEPTION("Internal error: account not found in list");
+
+ it_p = accountList.find(parent.id());
+ if(it_p == accountList.end())
+ throw new MYMONEYEXCEPTION("Internal error: parent account not found in list");
+
+ if(!account.institutionId().isEmpty())
+ throw new MYMONEYEXCEPTION("Cannot remove account still attached to an institution");
+
+ // FIXME: check referential integrity for the account to be removed
+
+ // check if the new info is based on the old one.
+ // this is the case, when the file and the id
+ // as well as the type are equal.
+ if((*it_a).id() == account.id()
+ && (*it_a).accountType() == account.accountType()) {
+
+ // second round over sub-ordinate accounts: do re-parenting
+ // but only if the list contains at least one entry
+ // FIXME: move this logic to MyMoneyFile
+ if((*it_a).accountList().count() > 0) {
+ for(it = (*it_a).accountList().begin(); it != (*it_a).accountList().end(); ++it) {
+ MyMoneyAccount acc(MyMoneyDatabaseMgr::account(*it));
+ reparentAccount(acc, parent);//, false);
+ }
+ }
+ // remove account from parent's list
+ parent.removeAccountId(account.id());
+ m_sql->modifyAccount(parent);
+
+ // remove account from the global account pool
+ //m_accountList.remove(account.id());
+
+ // remove from balance list
+//FIXME: m_balanceCache.remove(account.id());
+//FIXME: invalidateBalanceCache(parent.id());
+
+ m_sql->removeAccount(account);
+ }
+ commitTransaction();
+}
+
+void MyMoneyDatabaseMgr::removeInstitution(const MyMoneyInstitution& institution)
+{
+ QMap<QString, MyMoneyInstitution> institutionList = m_sql->fetchInstitutions(QString(institution.id()));
+ QMap<QString, MyMoneyInstitution>::ConstIterator it_i;
+
+ it_i = institutionList.find(institution.id());
+ if(it_i != institutionList.end()) {
+ // mark file as changed
+ m_sql->removeInstitution(institution);
+ } else
+ throw new MYMONEYEXCEPTION("invalid institution");
+}
+
+const MyMoneyTransaction MyMoneyDatabaseMgr::transaction(const QString& id) const
+{
+ // get the full key of this transaction, throw exception
+ // if it's invalid (unknown)
+ //if(!m_transactionKeys.contains(id))
+ // throw new MYMONEYEXCEPTION("invalid transaction id");
+
+ // check if this key is in the list, throw exception if not
+ //QString key = m_transactionKeys[id];
+ QMap <QString, MyMoneyTransaction> transactionList = m_sql->fetchTransactions("('" + QString(id) + "')");
+
+ //there should only be one transaction in the map, if it was found, so check the size of the map
+ //return the first element.
+ //if(!transactionList.contains(key))
+ if(!transactionList.size())
+ throw new MYMONEYEXCEPTION("invalid transaction key");
+
+ return transactionList.begin().data();
+}
+
+const MyMoneyMoney MyMoneyDatabaseMgr::balance(const QString& id, const QDate& date) const
+{
+ QStringList idList;
+ idList.append(id);
+ QMap<QString,MyMoneyMoney> tempMap = m_sql->fetchBalance(idList, date);
+
+ MyMoneyMoney returnValue = tempMap[id];
+ if (returnValue != MyMoneyMoney()) {
+ return returnValue;
+ }
+
+//DEBUG
+ QDate date_ (date);
+ //if (date_ == QDate()) date_ = QDate::currentDate();
+// END DEBUG
+
+ MyMoneyMoney result(0);
+ MyMoneyAccount acc;
+ QMap<QString, MyMoneyAccount> accountList = m_sql->fetchAccounts(/*QString(id)*/);
+ //QMap<QString, MyMoneyAccount>::const_iterator accpos = accountList.find(id);
+ if (date_ != QDate()) qDebug ("request balance for %s at %s", id.data(), date_.toString(Qt::ISODate).latin1());
+// if(!date_.isValid() && MyMoneyFile::instance()->account(id).accountType() != MyMoneyAccount::Stock) {
+// if(accountList.find(id) != accountList.end())
+// return accountList[id].balance();
+// return MyMoneyMoney(0);
+// }
+ if(/*m_balanceCache[id].valid == false || date != m_balanceCacheDate) || */ m_sql != 0) {
+ QMap<QString, MyMoneyMoney> balances;
+ QMap<QString, MyMoneyMoney>::ConstIterator it_b;
+//FIXME: if (date != m_balanceCacheDate) {
+//FIXME: m_balanceCache.clear();
+//FIXME: m_balanceCacheDate = date;
+//FIXME: }
+
+ QValueList<MyMoneyTransaction>::ConstIterator it_t;
+ QValueList<MyMoneyTransaction>::ConstIterator txEnd;
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+
+ MyMoneyTransactionFilter filter;
+ filter.addAccount(id);
+ filter.setDateFilter(QDate(), date_);
+ filter.setReportAllSplits(false);
+ QValueList<MyMoneyTransaction> list = transactionList(filter);
+
+ txEnd = list.end();
+ for(it_t = list.begin(); it_t != txEnd; ++it_t) {
+ for(it_s = (*it_t).splits().begin(); it_s != (*it_t).splits().end(); ++it_s){
+ const QString aid = (*it_s).accountId();
+ if((*it_s).action() == MyMoneySplit::ActionSplitShares) {
+ balances[aid] = balances[aid] * (*it_s).shares();
+ } else {
+ balances[aid] += (*it_s).value((*it_t).commodity(), accountList[aid].currencyId());
+ }
+ }
+ }
+
+ // fill the found balances into the cache
+//FIXME: for(it_b = balances.begin(); it_b != balances.end(); ++it_b) {
+//FIXME: MyMoneyBalanceCacheItem balance(*it_b);
+//FIXME: m_balanceCache[it_b.key()] = balance;
+//FIXME: }
+
+ // fill all accounts w/o transactions to zero
+// if (m_sql != 0) {
+// QMap<QString, MyMoneyAccount>::ConstIterator it_a;
+// for(it_a = m_accountList.begin(); it_a != m_accountList.end(); ++it_a) {
+//FIXME: if(m_balanceCache[(*it_a).id()].valid == false) {
+//FIXME: MyMoneyBalanceCacheItem balance(MyMoneyMoney(0,1));
+//FIXME: m_balanceCache[(*it_a).id()] = balance;
+//FIXME: }
+// }
+// }
+
+ result = balances[id];
+
+ }
+
+//FIXME: if(m_balanceCache[id].valid == true)
+//FIXME: result = m_balanceCache[id].balance;
+//FIXME: else
+//FIXME: qDebug("Cache mishit should never happen at this point");
+
+ return result;
+}
+
+const MyMoneyTransaction MyMoneyDatabaseMgr::transaction(const QString& account, const int idx) const
+{
+/* removed with MyMoneyAccount::Transaction
+ QMap<QString, MyMoneyAccount>::ConstIterator acc;
+
+ // find account object in list, throw exception if unknown
+ acc = m_accountList.find(account);
+ if(acc == m_accountList.end())
+ throw new MYMONEYEXCEPTION("unknown account id");
+
+ // get the transaction info from the account
+ MyMoneyAccount::Transaction t = (*acc).transaction(idx);
+
+ // return the transaction, throw exception if not found
+ return transaction(t.transactionID());
+*/
+
+ // new implementation if the above code does not work anymore
+ QValueList<MyMoneyTransaction> list;
+ //MyMoneyAccount acc = m_accountList[account];
+ MyMoneyAccount acc = m_sql->fetchAccounts(QString(account)) [account];
+ MyMoneyTransactionFilter filter;
+
+ if(acc.accountGroup() == MyMoneyAccount::Income
+ || acc.accountGroup() == MyMoneyAccount::Expense)
+ filter.addCategory(account);
+ else
+ filter.addAccount(account);
+
+ transactionList(list, filter);
+ if(idx < 0 || idx >= static_cast<int> (list.count()))
+ throw new MYMONEYEXCEPTION("Unknown idx for transaction");
+
+ return transaction(list[idx].id());
+}
+
+unsigned int MyMoneyDatabaseMgr::institutionCount(void) const
+{
+ return m_sql->getRecCount("kmmInstitutions");
+}
+
+void MyMoneyDatabaseMgr::accountList(QValueList<MyMoneyAccount>& list) const
+{
+ QMap <QString, MyMoneyAccount> accountList;
+ if (m_sql) accountList = m_sql->fetchAccounts();
+ QMap<QString, MyMoneyAccount>::ConstIterator it;
+ QMap<QString, MyMoneyAccount>::ConstIterator accEnd = accountList.end();
+ for(it = accountList.begin(); it != accEnd; ++it) {
+ if(!isStandardAccount((*it).id())) {
+ list.append(*it);
+ }
+ }
+}
+
+const MyMoneyAccount MyMoneyDatabaseMgr::liability(void) const
+{ return MyMoneyFile::instance()->account(STD_ACC_LIABILITY); }
+
+const MyMoneyAccount MyMoneyDatabaseMgr::asset(void) const
+{ return MyMoneyFile::instance()->account(STD_ACC_ASSET); }
+
+const MyMoneyAccount MyMoneyDatabaseMgr::expense(void) const
+{ return MyMoneyFile::instance()->account(STD_ACC_EXPENSE); }
+
+const MyMoneyAccount MyMoneyDatabaseMgr::income(void) const
+{ return MyMoneyFile::instance()->account(STD_ACC_INCOME); }
+
+const MyMoneyAccount MyMoneyDatabaseMgr::equity(void) const
+{ return MyMoneyFile::instance()->account(STD_ACC_EQUITY); }
+
+void MyMoneyDatabaseMgr::addSecurity(MyMoneySecurity& security)
+{
+ // create the account
+ MyMoneySecurity newSecurity(nextSecurityID(), security);
+
+ m_sql->addSecurity(newSecurity);
+ security = newSecurity;
+}
+
+void MyMoneyDatabaseMgr::modifySecurity(const MyMoneySecurity& security)
+{
+ QMap<QString, MyMoneySecurity> securitiesList = m_sql->fetchSecurities(QString(security.id()), true);
+ QMap<QString, MyMoneySecurity>::ConstIterator it;
+
+ it = securitiesList.find(security.id());
+ if(it == securitiesList.end())
+ {
+ QString msg = "Unknown security '";
+ msg += security.id() + "' during modifySecurity()";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+
+ m_sql->modifySecurity(security);
+}
+
+void MyMoneyDatabaseMgr::removeSecurity(const MyMoneySecurity& security)
+{
+ QMap<QString, MyMoneySecurity> securitiesList = m_sql->fetchSecurities(QString(security.id()));
+ QMap<QString, MyMoneySecurity>::ConstIterator it;
+
+ // FIXME: check referential integrity
+
+ it = securitiesList.find(security.id());
+ if(it == securitiesList.end())
+ {
+ QString msg = "Unknown security '";
+ msg += security.id() + "' during removeSecurity()";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+
+ m_sql->removeSecurity(security);
+}
+
+const MyMoneySecurity MyMoneyDatabaseMgr::security(const QString& id) const
+{
+ QMap<QString, MyMoneySecurity> securitiesList = m_sql->fetchSecurities(QString(id));
+ QMap<QString, MyMoneySecurity>::ConstIterator it = securitiesList.find(id);
+ if(it != securitiesList.end())
+ {
+ return it.data();
+ }
+
+ return MyMoneySecurity();
+}
+
+const QValueList<MyMoneySecurity> MyMoneyDatabaseMgr::securityList(void) const
+{ return m_sql->fetchSecurities().values(); }
+
+void MyMoneyDatabaseMgr::addPrice(const MyMoneyPrice& price)
+{
+ MyMoneyPriceEntries::ConstIterator it;
+ MyMoneyPriceList priceList = m_sql->fetchPrices();
+ it = priceList[MyMoneySecurityPair(price.from(), price.to())].find(price.date());
+ // do not replace, if the information did not change.
+ if(it != priceList[MyMoneySecurityPair(price.from(), price.to())].end()) {
+ if((*it).rate((*it).to()) == price.rate(price.to())
+ && (*it).source() == price.source())
+ return;
+ }
+
+ m_sql->addPrice(price);
+}
+
+void MyMoneyDatabaseMgr::removePrice(const MyMoneyPrice& price)
+{
+ m_sql->removePrice(price);
+}
+
+const MyMoneyPrice MyMoneyDatabaseMgr::price(const QString& fromId, const QString& toId, const QDate& _date, const bool exactDate) const
+{
+ return m_sql->fetchSinglePrice(fromId, toId, _date, exactDate);
+}
+
+const MyMoneyPriceList MyMoneyDatabaseMgr::priceList(void) const
+{ return m_sql->fetchPrices(); }
+
+void MyMoneyDatabaseMgr::addSchedule(MyMoneySchedule& sched)
+{
+ // first perform all the checks
+ if(!sched.id().isEmpty())
+ throw new MYMONEYEXCEPTION("schedule already contains an id");
+
+ // The following will throw an exception when it fails
+ sched.validate(false);
+
+ if (m_sql) {
+ startTransaction();
+ sched = MyMoneySchedule (nextScheduleID(), sched);
+
+ m_sql->addSchedule(sched);
+ commitTransaction();
+ }
+}
+
+void MyMoneyDatabaseMgr::modifySchedule(const MyMoneySchedule& sched)
+{
+ QMap<QString, MyMoneySchedule> scheduleList = m_sql->fetchSchedules(QString(sched.id()));
+ QMap<QString, MyMoneySchedule>::ConstIterator it;
+
+ it = scheduleList.find(sched.id());
+ if(it == scheduleList.end()) {
+ QString msg = "Unknown schedule '" + sched.id() + "'";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+
+ m_sql->modifySchedule(sched);
+}
+
+void MyMoneyDatabaseMgr::removeSchedule(const MyMoneySchedule& sched)
+{
+ QMap<QString, MyMoneySchedule> scheduleList = m_sql->fetchSchedules(QString(sched.id()));
+ QMap<QString, MyMoneySchedule>::ConstIterator it;
+
+ it = scheduleList.find(sched.id());
+ if(it == scheduleList.end()) {
+ QString msg = "Unknown schedule '" + sched.id() + "'";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+
+ // FIXME: check referential integrity for loan accounts
+
+ m_sql->removeSchedule(sched);
+}
+
+const MyMoneySchedule MyMoneyDatabaseMgr::schedule(const QString& id) const
+{
+ QMap<QString, MyMoneySchedule> scheduleList = m_sql->fetchSchedules(QString(id));
+ QMap<QString, MyMoneySchedule>::ConstIterator pos;
+
+ // locate the schedule and if present, return it's data
+ pos = scheduleList.find(id);
+ if(pos != scheduleList.end())
+ return (*pos);
+
+ // throw an exception, if it does not exist
+ QString msg = "Unknown schedule id '" + id + "'";
+ throw new MYMONEYEXCEPTION(msg);
+}
+
+const QValueList<MyMoneySchedule> MyMoneyDatabaseMgr::scheduleList(const QString& accountId,
+ const MyMoneySchedule::typeE type,
+ const MyMoneySchedule::occurenceE occurence,
+ const MyMoneySchedule::paymentTypeE paymentType,
+ const QDate& startDate,
+ const QDate& endDate,
+ const bool overdue) const
+{
+ QMap<QString, MyMoneySchedule> scheduleList;
+ if (m_sql) scheduleList = m_sql->fetchSchedules();
+ QMap<QString, MyMoneySchedule>::ConstIterator pos;
+ QValueList<MyMoneySchedule> list;
+
+ // qDebug("scheduleList()");
+
+ for(pos = scheduleList.begin(); pos != scheduleList.end(); ++pos) {
+ // qDebug(" '%s'", (*pos).id().data());
+
+ if(type != MyMoneySchedule::TYPE_ANY) {
+ if(type != (*pos).type()) {
+ continue;
+ }
+ }
+
+ if(occurence != MyMoneySchedule::OCCUR_ANY) {
+ if(occurence != (*pos).occurence()) {
+ continue;
+ }
+ }
+
+ if(paymentType != MyMoneySchedule::STYPE_ANY) {
+ if(paymentType != (*pos).paymentType()) {
+ continue;
+ }
+ }
+
+ if(!accountId.isEmpty()) {
+ MyMoneyTransaction t = (*pos).transaction();
+ QValueList<MyMoneySplit>::ConstIterator it;
+ QValueList<MyMoneySplit> splits;
+ splits = t.splits();
+ for(it = splits.begin(); it != splits.end(); ++it) {
+ if((*it).accountId() == accountId)
+ break;
+ }
+ if(it == splits.end()) {
+ continue;
+ }
+ }
+
+ if(startDate.isValid() && endDate.isValid()) {
+ if((*pos).paymentDates(startDate, endDate).count() == 0) {
+ continue;
+ }
+ }
+
+ if(startDate.isValid() && !endDate.isValid()) {
+ if(!(*pos).nextPayment(startDate.addDays(-1)).isValid()) {
+ continue;
+ }
+ }
+
+ if(!startDate.isValid() && endDate.isValid()) {
+ if((*pos).startDate() > endDate) {
+ continue;
+ }
+ }
+
+ if(overdue) {
+ if (!(*pos).isOverdue())
+ continue;
+/*
+ QDate nextPayment = (*pos).nextPayment((*pos).lastPayment());
+ if(!nextPayment.isValid())
+ continue;
+ if(nextPayment >= QDate::currentDate())
+ continue;
+*/
+ }
+
+ // qDebug("Adding '%s'", (*pos).name().latin1());
+ list << *pos;
+ }
+ return list;
+}
+
+const QValueList<MyMoneySchedule> MyMoneyDatabaseMgr::scheduleListEx( int scheduleTypes,
+ int scheduleOcurrences,
+ int schedulePaymentTypes,
+ QDate startDate,
+ const QStringList& accounts) const
+{
+// qDebug("scheduleListEx");
+ QMap<QString, MyMoneySchedule> scheduleList = m_sql->fetchSchedules();
+ QMap<QString, MyMoneySchedule>::ConstIterator pos;
+ QValueList<MyMoneySchedule> list;
+
+ if (!startDate.isValid())
+ return list;
+
+ for(pos = scheduleList.begin(); pos != scheduleList.end(); ++pos)
+ {
+ if (scheduleTypes && !(scheduleTypes & (*pos).type()))
+ continue;
+
+ if (scheduleOcurrences && !(scheduleOcurrences & (*pos).occurence()))
+ continue;
+
+ if (schedulePaymentTypes && !(schedulePaymentTypes & (*pos).paymentType()))
+ continue;
+
+ if((*pos).paymentDates(startDate, startDate).count() == 0)
+ continue;
+
+ if ((*pos).isFinished())
+ continue;
+
+ if ((*pos).hasRecordedPayment(startDate))
+ continue;
+
+ if (accounts.count() > 0)
+ {
+ if (accounts.contains((*pos).account().id()))
+ continue;
+ }
+
+// qDebug("\tAdding '%s'", (*pos).name().latin1());
+ list << *pos;
+ }
+
+ return list;
+}
+
+void MyMoneyDatabaseMgr::addCurrency(const MyMoneySecurity& currency)
+{
+ if (m_sql) {
+ QMap<QString, MyMoneySecurity> currencyList = m_sql->fetchCurrencies(QString(currency.id()));
+ QMap<QString, MyMoneySecurity>::ConstIterator it;
+
+ it = currencyList.find(currency.id());
+ if(it != currencyList.end()) {
+ throw new MYMONEYEXCEPTION(QString("Cannot add currency with existing id %1").arg(currency.id().data()));
+ }
+
+ m_sql->addCurrency(currency);
+ }
+}
+
+void MyMoneyDatabaseMgr::modifyCurrency(const MyMoneySecurity& currency)
+{
+ QMap<QString, MyMoneySecurity> currencyList = m_sql->fetchCurrencies(QString(currency.id()));
+ QMap<QString, MyMoneySecurity>::ConstIterator it;
+
+ it = currencyList.find(currency.id());
+ if(it == currencyList.end()) {
+ throw new MYMONEYEXCEPTION(QString("Cannot modify currency with unknown id %1").arg(currency.id().data()));
+ }
+
+ m_sql->modifyCurrency(currency);
+}
+
+void MyMoneyDatabaseMgr::removeCurrency(const MyMoneySecurity& currency)
+{
+ QMap<QString, MyMoneySecurity> currencyList = m_sql->fetchCurrencies(QString(currency.id()));
+ QMap<QString, MyMoneySecurity>::ConstIterator it;
+
+ // FIXME: check referential integrity
+
+ it = currencyList.find(currency.id());
+ if(it == currencyList.end()) {
+ throw new MYMONEYEXCEPTION(QString("Cannot remove currency with unknown id %1").arg(currency.id().data()));
+ }
+
+ m_sql->removeCurrency(currency);
+}
+
+const MyMoneySecurity MyMoneyDatabaseMgr::currency(const QString& id) const
+{
+ if(id.isEmpty()) {
+
+ }
+ QMap<QString, MyMoneySecurity> currencyList = m_sql->fetchCurrencies(QString(id));
+ QMap<QString, MyMoneySecurity>::ConstIterator it;
+
+ it = currencyList.find(id);
+ if(it == currencyList.end()) {
+ throw new MYMONEYEXCEPTION(QString("Cannot retrieve currency with unknown id '%1'").arg(id.data()));
+ }
+
+ return *it;
+}
+
+const QValueList<MyMoneySecurity> MyMoneyDatabaseMgr::currencyList(void) const
+{
+ if (m_sql) {
+ return m_sql->fetchCurrencies().values();
+ } else {
+ return QValueList<MyMoneySecurity> ();
+ }
+}
+
+const QValueList<MyMoneyReport> MyMoneyDatabaseMgr::reportList( void ) const
+{
+ if (m_sql) {
+ return m_sql->fetchReports().values();
+ } else {
+ return QValueList<MyMoneyReport> ();
+ }
+}
+
+void MyMoneyDatabaseMgr::addReport( MyMoneyReport& report )
+{
+ if(!report.id().isEmpty())
+ throw new MYMONEYEXCEPTION("transaction already contains an id");
+
+ MyMoneyReport newReport(nextReportID(), report);
+ report = newReport;
+ m_sql->addReport(newReport);
+ //m_sql->addReport(MyMoneyReport (nextReportID(), report));
+}
+
+void MyMoneyDatabaseMgr::modifyReport( const MyMoneyReport& report )
+{
+ QMap<QString, MyMoneyReport> reportList = m_sql->fetchReports(QString(report.id()));
+ QMap<QString, MyMoneyReport>::ConstIterator it;
+
+ it = reportList.find(report.id());
+ if(it == reportList.end()) {
+ QString msg = "Unknown report '" + report.id() + "'";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+
+ m_sql->modifyReport(report);
+}
+
+unsigned MyMoneyDatabaseMgr::countReports( void ) const
+{
+ return m_sql->getRecCount("kmmReports");
+}
+
+const MyMoneyReport MyMoneyDatabaseMgr::report( const QString& id ) const
+{
+ return m_sql->fetchReports(QString(id))[id];
+}
+
+void MyMoneyDatabaseMgr::removeReport(const MyMoneyReport& report)
+{
+ QMap<QString, MyMoneyReport> reportList = m_sql->fetchReports(QString(report.id()));
+ QMap<QString, MyMoneyReport>::ConstIterator it;
+
+ it = reportList.find(report.id());
+ if(it == reportList.end()) {
+ QString msg = "Unknown report '" + report.id() + "'";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+
+ m_sql->removeReport(report);
+}
+
+const QValueList<MyMoneyBudget> MyMoneyDatabaseMgr::budgetList( void ) const
+{
+ return m_sql->fetchBudgets().values();
+}
+
+void MyMoneyDatabaseMgr::addBudget( MyMoneyBudget& budget )
+{
+ MyMoneyBudget newBudget(nextBudgetID(), budget);
+ m_sql->addBudget(newBudget);
+}
+
+const MyMoneyBudget MyMoneyDatabaseMgr::budgetByName(const QString& budget) const
+{
+ QMap<QString, MyMoneyBudget> budgets = m_sql->fetchBudgets();
+ QMap<QString, MyMoneyBudget>::ConstIterator it_p;
+
+ for(it_p = budgets.begin(); it_p != budgets.end(); ++it_p) {
+ if((*it_p).name() == budget) {
+ return *it_p;
+ }
+ }
+
+ throw new MYMONEYEXCEPTION("Unknown budget '" + budget + "'");
+}
+
+void MyMoneyDatabaseMgr::modifyBudget( const MyMoneyBudget& budget )
+{
+ //QMap<QString, MyMoneyBudget>::ConstIterator it;
+
+ //it = m_budgetList.find(budget.id());
+ //if(it == m_budgetList.end()) {
+ // QString msg = "Unknown budget '" + budget.id() + "'";
+ // throw new MYMONEYEXCEPTION(msg);
+ //}
+ //m_budgetList.modify(budget.id(), budget);
+
+ startTransaction();
+ if (m_sql->fetchBudgets(QString(budget.id()), true).empty()) {
+ QString msg = "Unknown budget '" + budget.id() + "'";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+ m_sql->modifyBudget(budget);
+ commitTransaction();
+}
+
+unsigned MyMoneyDatabaseMgr::countBudgets( void ) const
+{
+ return m_sql->getRecCount("kmmBudgetConfig");
+}
+
+MyMoneyBudget MyMoneyDatabaseMgr::budget( const QString& id ) const
+{
+ return m_sql->fetchBudgets(QString(id)) [id];
+}
+
+void MyMoneyDatabaseMgr::removeBudget(const MyMoneyBudget& budget)
+{
+// QMap<QString, MyMoneyBudget>::ConstIterator it;
+//
+// it = m_budgetList.find(budget.id());
+// if(it == m_budgetList.end()) {
+// QString msg = "Unknown budget '" + budget.id() + "'";
+// throw new MYMONEYEXCEPTION(msg);
+// }
+//
+ m_sql->removeBudget(budget);
+}
+
+void MyMoneyDatabaseMgr::clearCache(void)
+{
+ //m_balanceCache.clear();
+}
+
+class isReferencedHelper {
+ public:
+ isReferencedHelper(const QString& id)
+ : m_id (id)
+ {}
+
+ inline bool operator() (const MyMoneyObject& obj) const
+ { return obj.hasReferenceTo(m_id); }
+
+ private:
+ QString m_id;
+};
+
+bool MyMoneyDatabaseMgr::isReferenced(const MyMoneyObject& obj, const MyMoneyFileBitArray& skipCheck) const
+{
+ bool rc = false;
+ const QString& id = obj.id();
+
+ MyMoneyPriceList::const_iterator it_pr;
+
+ MyMoneyPriceList::const_iterator priceEnd;
+
+ // FIXME optimize the list of objects we have to checks
+ // with a bit of knowledge of the internal structure, we
+ // could optimize the number of objects we check for references
+
+ // Scan all engine objects for a reference
+ if(!skipCheck[RefCheckTransaction]) {
+ bool skipTransactions = false;
+ MyMoneyTransactionFilter f;
+ if (typeid(obj) == typeid(MyMoneyAccount)) {
+ f.addAccount(obj.id());
+ } else if (typeid(obj) == typeid(MyMoneyCategory)) {
+ f.addCategory(obj.id());
+ } else if (typeid(obj) == typeid(MyMoneyPayee)) {
+ f.addPayee(obj.id());
+ } // if it's anything else, I guess we just read everything
+ //FIXME: correction, transactions can only have a reference to an account or payee,
+ // so, read nothing.
+ else {
+ skipTransactions = true;
+ }
+ if (! skipTransactions) {
+ //QMap <QString, MyMoneyTransaction> transactionList = m_sql->fetchTransactions(f);
+ //rc = (transactionList.end() != std::find_if(transactionList.begin(), transactionList.end(), isReferencedHelper(id)));
+ //if (rc != m_sql->isReferencedByTransaction(obj.id()))
+ // qDebug ("Transaction match inconsistency.");
+ rc = m_sql->isReferencedByTransaction(obj.id());
+ }
+ }
+
+ if(!skipCheck[RefCheckAccount] && !rc) {
+ QValueList<MyMoneyAccount> accountList;
+ MyMoneyFile::instance()->accountList(accountList);
+ rc = (accountList.end() != std::find_if(accountList.begin(), accountList.end(), isReferencedHelper(id)));
+ }
+ if(!skipCheck[RefCheckInstitution] && !rc) {
+ QValueList<MyMoneyInstitution> institutionList;
+ MyMoneyFile::instance()->institutionList(institutionList);
+ rc = (institutionList.end() != std::find_if(institutionList.begin(), institutionList.end(), isReferencedHelper(id)));
+ }
+ if(!skipCheck[RefCheckPayee] && !rc) {
+ QValueList<MyMoneyPayee> payeeList = MyMoneyFile::instance()->payeeList();
+ rc = (payeeList.end() != std::find_if(payeeList.begin(), payeeList.end(), isReferencedHelper(id)));
+ }
+ if(!skipCheck[RefCheckReport] && !rc) {
+ QMap<QString, MyMoneyReport> reportList = m_sql->fetchReports();
+ rc = (reportList.end() != std::find_if(reportList.begin(), reportList.end(), isReferencedHelper(id)));
+ }
+ if(!skipCheck[RefCheckBudget] && !rc) {
+ QMap<QString, MyMoneyBudget> budgets = m_sql->fetchBudgets();
+ rc = (budgets.end() != std::find_if(budgets.begin(), budgets.end(), isReferencedHelper(id)));
+ }
+ if(!skipCheck[RefCheckSchedule] && !rc) {
+ QMap<QString, MyMoneySchedule> scheduleList = m_sql->fetchSchedules();
+ rc = (scheduleList.end() != std::find_if(scheduleList.begin(), scheduleList.end(), isReferencedHelper(id)));
+ }
+ if(!skipCheck[RefCheckSecurity] && !rc) {
+ QValueList<MyMoneySecurity> securitiesList = MyMoneyFile::instance()->securityList();
+ rc = (securitiesList.end() != std::find_if(securitiesList.begin(), securitiesList.end(), isReferencedHelper(id)));
+ }
+ if(!skipCheck[RefCheckCurrency] && !rc) {
+ QValueList<MyMoneySecurity> currencyList = m_sql->fetchCurrencies().values();
+ rc = (currencyList.end() != std::find_if(currencyList.begin(), currencyList.end(), isReferencedHelper(id)));
+ }
+ // within the pricelist we don't have to scan each entry. Checking the QPair
+ // members of the MyMoneySecurityPair is enough as they are identical to the
+ // two security ids
+ if(!skipCheck[RefCheckPrice] && !rc) {
+ MyMoneyPriceList priceList = m_sql->fetchPrices();
+ priceEnd = priceList.end();
+ for(it_pr = priceList.begin(); !rc && it_pr != priceEnd; ++it_pr) {
+ rc = (it_pr.key().first == id) || (it_pr.key().second == id);
+ }
+ }
+ return rc;
+}
+
+void MyMoneyDatabaseMgr::close(void) {
+ if (m_sql != 0) {
+ m_sql->close(true);
+ m_sql = 0;
+ }
+}
+
+void MyMoneyDatabaseMgr::startTransaction(void)
+{ if (m_sql) m_sql->startCommitUnit ("databasetransaction"); }
+
+bool MyMoneyDatabaseMgr::commitTransaction(void)
+{
+ if (m_sql)
+ return m_sql->endCommitUnit ("databasetransaction");
+ return false;
+}
+
+void MyMoneyDatabaseMgr::rollbackTransaction(void)
+{ if (m_sql) m_sql->cancelCommitUnit ("databasetransaction"); }
+
+void MyMoneyDatabaseMgr::setCreationDate(const QDate& val)
+{ m_creationDate = val; }
+
+KSharedPtr <MyMoneyStorageSql> MyMoneyDatabaseMgr::connectToDatabase(const KURL& url) {
+ m_sql = new MyMoneyStorageSql (this, url);
+ return m_sql;
+}
+
+ void MyMoneyDatabaseMgr::fillStorage()
+{ m_sql->fillStorage(); }
+
+void MyMoneyDatabaseMgr::setLastModificationDate(const QDate& val)
+{ m_lastModificationDate = val; }
+
+bool MyMoneyDatabaseMgr::isDuplicateTransaction(const QString& /*id*/) const
+{
+ //FIXME: figure out the real id from the key and check the DB.
+//return m_transactionKeys.contains(id);
+ return false;
+}
+
+void MyMoneyDatabaseMgr::loadAccounts(const QMap<QString, MyMoneyAccount>& /*map*/)
+{
+// m_accountList = map;
+//FIXME: update the database.
+// startTransaction
+// DELETE FROM kmmAccounts
+// for each account in the map
+// m_sql->addAccount(...)
+// commitTransaction
+// on error, rollbackTransaction
+}
+
+void MyMoneyDatabaseMgr::loadTransactions(const QMap<QString, MyMoneyTransaction>& /*map*/)
+{
+// m_transactionList = map;
+//FIXME: update the database.
+
+// // now fill the key map
+// QMap<QString, QString> keys;
+// QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
+// for(it_t = map.begin(); it_t != map.end(); ++it_t) {
+// keys[(*it_t).id()] = it_t.key();
+// }
+// m_transactionKeys = keys;
+}
+
+void MyMoneyDatabaseMgr::loadInstitutions(const QMap<QString, MyMoneyInstitution>& /*map*/)
+{
+// m_institutionList = map;
+//FIXME: update the database.
+
+// // now fill the key map
+// QMap<QString, QString> keys;
+// QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
+// for(it_t = map.begin(); it_t != map.end(); ++it_t) {
+// keys[(*it_t).id()] = it_t.key();
+// }
+// m_transactionKeys = keys;
+}
+
+void MyMoneyDatabaseMgr::loadPayees(const QMap<QString, MyMoneyPayee>& /*map*/)
+{
+// m_payeeList = map;
+}
+
+void MyMoneyDatabaseMgr::loadSchedules(const QMap<QString, MyMoneySchedule>& /*map*/)
+{
+// m_scheduleList = map;
+}
+
+void MyMoneyDatabaseMgr::loadSecurities(const QMap<QString, MyMoneySecurity>& /*map*/)
+{
+// m_securitiesList = map;
+}
+
+void MyMoneyDatabaseMgr::loadCurrencies(const QMap<QString, MyMoneySecurity>& /*map*/)
+{
+// m_currencyList = map;
+//FIXME: update the database.
+// startTransaction
+// DELETE FROM kmmBudgetConfig
+// for each budget in the map
+// m_sql->addBudget(...)
+// commitTransaction
+// on error, rollbackTransaction
+}
+
+void MyMoneyDatabaseMgr::loadReports( const QMap<QString, MyMoneyReport>& /*reports*/ )
+{
+// m_reportList = reports;
+//FIXME: update the database.
+// startTransaction
+// DELETE FROM kmmBudgetConfig
+// for each budget in the map
+// m_sql->addBudget(...)
+// commitTransaction
+// on error, rollbackTransaction
+}
+
+void MyMoneyDatabaseMgr::loadBudgets( const QMap<QString, MyMoneyBudget>& /*budgets*/ )
+{
+// m_budgetList = budgets;
+//FIXME: update the database.
+// startTransaction
+// DELETE FROM kmmBudgetConfig
+// for each budget in the map
+// m_sql->addBudget(...)
+// commitTransaction
+// on error, rollbackTransaction
+}
+
+void MyMoneyDatabaseMgr::loadPrices(const MyMoneyPriceList& list)
+{
+ Q_UNUSED(list);
+}
+
+unsigned long MyMoneyDatabaseMgr::accountId(void) const
+{ return m_sql->getNextAccountId(); }
+
+unsigned long MyMoneyDatabaseMgr::transactionId(void) const
+{ return m_sql->getNextTransactionId(); }
+
+unsigned long MyMoneyDatabaseMgr::payeeId(void) const
+{ return m_sql->getNextPayeeId(); }
+
+unsigned long MyMoneyDatabaseMgr::institutionId(void) const
+{ return m_sql->getNextInstitutionId(); }
+
+unsigned long MyMoneyDatabaseMgr::scheduleId(void) const
+{ return m_sql->getNextScheduleId(); }
+
+unsigned long MyMoneyDatabaseMgr::securityId(void) const
+{ return m_sql->getNextSecurityId(); }
+
+unsigned long MyMoneyDatabaseMgr::reportId(void) const
+{ return m_sql->getNextReportId(); }
+
+unsigned long MyMoneyDatabaseMgr::budgetId(void) const
+{ return m_sql->getNextBudgetId(); }
+
+void MyMoneyDatabaseMgr::loadAccountId(const unsigned long id)
+{
+ m_sql->loadAccountId(id);
+}
+
+void MyMoneyDatabaseMgr::loadTransactionId(const unsigned long id)
+{
+ m_sql->loadTransactionId(id);
+}
+
+void MyMoneyDatabaseMgr::loadPayeeId(const unsigned long id)
+{
+ m_sql->loadPayeeId(id);
+}
+
+void MyMoneyDatabaseMgr::loadInstitutionId(const unsigned long id)
+{
+ m_sql->loadInstitutionId(id);
+}
+
+void MyMoneyDatabaseMgr::loadScheduleId(const unsigned long id)
+{
+ m_sql->loadScheduleId(id);
+}
+
+void MyMoneyDatabaseMgr::loadSecurityId(const unsigned long id)
+{
+ m_sql->loadSecurityId(id);
+}
+
+void MyMoneyDatabaseMgr::loadReportId(const unsigned long id)
+{
+ m_sql->loadReportId(id);
+}
+
+void MyMoneyDatabaseMgr::loadBudgetId(const unsigned long id)
+{
+ m_sql->loadBudgetId(id);
+}
+
+void MyMoneyDatabaseMgr::rebuildAccountBalances(void)
+{
+ startTransaction();
+ QMap<QString, MyMoneyAccount> accountMap = m_sql->fetchAccounts(QStringList(), true);
+
+ QMap<QString, MyMoneyMoney> balanceMap = m_sql->fetchBalance(accountMap.keys(), QDate());
+
+ for (QMap<QString, MyMoneyMoney>::const_iterator it_b = balanceMap.begin();
+ it_b != balanceMap.end(); ++it_b) {
+ accountMap[it_b.key()].setBalance(it_b.data());
+ }
+
+ for (QMap<QString, MyMoneyAccount>::const_iterator it_a = accountMap.begin();
+ it_a != accountMap.end(); ++it_a) {
+ m_sql->modifyAccount(it_a.data());
+ }
+ commitTransaction();
+}
+
+void MyMoneyDatabaseMgr::removeReferences(const QString& id)
+{
+ QMap<QString, MyMoneyReport>::const_iterator it_r;
+ QMap<QString, MyMoneyBudget>::const_iterator it_b;
+
+ // remove from reports
+ QMap<QString, MyMoneyReport> reportList = m_sql->fetchReports();
+ for(it_r = reportList.begin(); it_r != reportList.end(); ++it_r) {
+ MyMoneyReport r = *it_r;
+ r.removeReference(id);
+// reportList.modify(r.id(), r);
+ }
+
+ // remove from budgets
+ QMap<QString, MyMoneyBudget> budgetList = m_sql->fetchBudgets();
+ for(it_b = budgetList.begin(); it_b != budgetList.end(); ++it_b) {
+ MyMoneyBudget b = *it_b;
+ b.removeReference(id);
+// budgetList.modify(b.id(), b);
+ }
+}
+
+#undef TRY
+#undef CATCH
+#undef PASS
diff --git a/kmymoney2/mymoney/storage/mymoneydatabasemgr.h b/kmymoney2/mymoney/storage/mymoneydatabasemgr.h
new file mode 100644
index 0000000..21bf8d6
--- /dev/null
+++ b/kmymoney2/mymoney/storage/mymoneydatabasemgr.h
@@ -0,0 +1,1038 @@
+/***************************************************************************
+ mymoneydatabasemgr.h - description
+ -------------------
+ begin : June 5 2007
+ copyright : (C) 2007 by Fernando Vilas
+ email : Fernando Vilas <fvilas@iname.com>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYDATABASEMGR_H
+#define MYMONEYDATABASEMGR_H
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "imymoneyserialize.h"
+#include "imymoneystorage.h"
+#include "mymoneymap.h"
+#include "mymoneystoragesql.h"
+
+/**
+ * The MyMoneyDatabaseMgr class represents the storage engine for databases.
+ * The actual connection and internal storage is handled through the
+ * MyMoneyStorageSql interface.
+ *
+ * The MyMoneyDatabaseMgr must have a MyMoneyStorageSql connected to a
+ * database to be useful. Once connected, data will be loaded from/sent to the
+ * database synchronously. The method dirty() will always return false. Making
+ * this many trips to the database is not very fast, so when possible, the
+ * data cache in MyMoneyFile is used.
+ *
+ */
+class MyMoneyDatabaseMgr : public IMyMoneyStorage, public IMyMoneySerialize,
+ public MyMoneyKeyValueContainer
+{
+public:
+ MyMoneyDatabaseMgr();
+ ~MyMoneyDatabaseMgr();
+
+ // general get functions
+ virtual const MyMoneyPayee user(void) const;
+ virtual const QDate creationDate(void) const;
+ virtual const QDate lastModificationDate(void) const;
+ virtual unsigned int currentFixVersion(void) const;
+ virtual unsigned int fileFixVersion(void) const;
+
+ // general set functions
+ virtual void setUser(const MyMoneyPayee& user);
+ virtual void setFileFixVersion(const unsigned int v);
+
+ // methods provided by MyMoneyKeyValueContainer
+ virtual void setValue(const QString& key, const QString& value);
+ virtual const QString value(const QString& key) const;
+ virtual void deletePair(const QString& key);
+
+ /**
+ * This method is used to duplicate an IMyMoneyStorage object and return
+ * a pointer to the newly created copy. The caller of this method is the
+ * new owner of the object and must destroy it.
+ */
+ virtual MyMoneyDatabaseMgr const * duplicate(void);
+
+ /**
+ * This method is used to create a new account
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param account MyMoneyAccount filled with data
+ */
+ virtual void addAccount(MyMoneyAccount& account);
+
+ /**
+ * This method is used to add one account as sub-ordinate to another
+ * (parent) account. The objects that are passed will be modified
+ * accordingly.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param parent parent account the account should be added to
+ * @param account the account to be added
+ */
+ virtual void addAccount(MyMoneyAccount& parent, MyMoneyAccount& account);
+
+ /**
+ * This method is used to create a new payee
+ *
+ * An exception will be thrown upon error conditions
+ *
+ * @param payee MyMoneyPayee reference to payee information
+ */
+ virtual void addPayee(MyMoneyPayee& payee);
+
+ /**
+ * This method is used to retrieve information about a payee
+ * An exception will be thrown upon error conditions.
+ *
+ * @param id QString reference to id of payee
+ *
+ * @return MyMoneyPayee object of payee
+ */
+ virtual const MyMoneyPayee payee(const QString& id) const;
+
+ /**
+ * This method is used to retrieve the id to a corresponding
+ * name of a payee/receiver.
+ * An exception will be thrown upon error conditions.
+ *
+ * @param payee QString reference to name of payee
+ *
+ * @return MyMoneyPayee object of payee
+ */
+ virtual const MyMoneyPayee payeeByName(const QString& payee) const;
+
+ /**
+ * This method is used to modify an existing payee
+ *
+ * An exception will be thrown upon error conditions
+ *
+ * @param payee MyMoneyPayee reference to payee information
+ */
+ virtual void modifyPayee(const MyMoneyPayee& payee);
+
+ /**
+ * This method is used to remove an existing payee
+ *
+ * An exception will be thrown upon error conditions
+ *
+ * @param payee MyMoneyPayee reference to payee information
+ */
+ virtual void removePayee(const MyMoneyPayee& payee);
+
+ /**
+ * This method returns a list of the payees
+ * inside a MyMoneyStorage object
+ *
+ * @return QValueList<MyMoneyPayee> containing the payee information
+ */
+ virtual const QValueList<MyMoneyPayee> payeeList(void) const;
+
+ /**
+ * Returns the account addressed by it's id.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param id id of the account to locate.
+ * @return reference to MyMoneyAccount object. An exception is thrown
+ * if the id is unknown
+ */
+ virtual const MyMoneyAccount account(const QString& id) const;
+
+ /**
+ * This method is used to check whether a given
+ * account id references one of the standard accounts or not.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param id account id
+ * @return true if account-id is one of the standards, false otherwise
+ */
+ virtual bool isStandardAccount(const QString& id) const;
+
+ /**
+ * This method is used to set the name for the specified standard account
+ * within the storage area. An exception will be thrown, if an error
+ * occurs
+ *
+ * @param id QString reference to one of the standard accounts.
+ * @param name QString reference to the name to be set
+ *
+ */
+ virtual void setAccountName(const QString& id, const QString& name);
+
+ /**
+ * Adds an institution to the storage. A
+ * respective institution-ID will be generated within this record.
+ * The ID is stored as QString in the object passed as argument.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param institution The complete institution information in a
+ * MyMoneyInstitution object
+ */
+ virtual void addInstitution(MyMoneyInstitution& institution);
+
+ /**
+ * Adds a transaction to the file-global transaction pool. A respective
+ * transaction-ID will be generated within this record. The ID is stored
+ * QString with the object.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param transaction reference to the transaction
+ * @param skipAccountUpdate if set, the transaction lists of the accounts
+ * referenced in the splits are not updated. This is used for
+ * bulk loading a lot of transactions but not during normal operation
+ */
+ virtual void addTransaction(MyMoneyTransaction& transaction, const bool skipAccountUpdate = false);
+
+ /**
+ * This method is used to determince, if the account with the
+ * given ID is referenced by any split in m_transactionList.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param id id of the account to be checked for
+ * @return true if account is referenced, false otherwise
+ */
+ virtual bool hasActiveSplits(const QString& id) const;
+
+ /**
+ * This method is used to return the actual balance of an account
+ * without it's sub-ordinate accounts. If a @p date is presented,
+ * the balance at the beginning of this date (not including any
+ * transaction on this date) is returned. Otherwise all recorded
+ * transactions are included in the balance.
+ *
+ * @param id id of the account in question
+ * @param date return balance for specific date
+ * @return balance of the account as MyMoneyMoney object
+ */
+ virtual const MyMoneyMoney balance(const QString& id, const QDate& date) const;
+
+ /**
+ * This method is used to return the actual balance of an account
+ * including it's sub-ordinate accounts. If a @p date is presented,
+ * the balance at the beginning of this date (not including any
+ * transaction on this date) is returned. Otherwise all recorded
+ * transactions are included in the balance.
+ *
+ * @param id id of the account in question
+ * @param date return balance for specific date
+ * @return balance of the account as MyMoneyMoney object
+ */
+ virtual const MyMoneyMoney totalBalance(const QString& id, const QDate& date) const;
+
+ /**
+ * Returns the institution of a given ID
+ *
+ * @param id id of the institution to locate
+ * @return MyMoneyInstitution object filled with data. If the institution
+ * could not be found, an exception will be thrown
+ */
+ virtual const MyMoneyInstitution institution(const QString& id) const;
+
+ /**
+ * This method returns an indicator if the storage object has been
+ * changed after it has last been saved to permanent storage.
+ *
+ * @return true if changed, false if not (for a database, always false).
+ */
+ virtual bool dirty(void) const;
+
+ /**
+ * This method can be used by an external object to force the
+ * storage object to be dirty. This is used e.g. when an upload
+ * to an external destination failed but the previous storage
+ * to a local disk was ok.
+ *
+ * Since the database is synchronized with the application, this method
+ * is a no-op.
+ */
+ virtual void setDirty(void);
+
+ /**
+ * This method returns the number of accounts currently known to this storage
+ * in the range 0..MAXUINT
+ *
+ * @return number of accounts currently known inside a MyMoneyFile object
+ */
+ virtual unsigned int accountCount(void) const;
+
+ /**
+ * This method returns a list of the institutions
+ * inside a MyMoneyStorage object
+ *
+ * @return QValueList<MyMoneyInstitution> containing the
+ * institution information
+ */
+ virtual const QValueList<MyMoneyInstitution> institutionList(void) const;
+
+ /**
+ * Modifies an already existing account in the file global account pool.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param account reference to the new account information
+ * @param skipCheck allows to skip the builtin consistency checks
+ */
+ virtual void modifyAccount(const MyMoneyAccount& account, const bool skipCheck = false);
+
+ /**
+ * Modifies an already existing institution in the file global
+ * institution pool.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param institution The complete new institution information
+ */
+ virtual void modifyInstitution(const MyMoneyInstitution& institution);
+
+ /**
+ * This method is used to update a specific transaction in the
+ * transaction pool of the MyMoneyFile object
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param transaction reference to transaction to be changed
+ */
+ virtual void modifyTransaction(const MyMoneyTransaction& transaction);
+
+ /**
+ * This method re-parents an existing account
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param account MyMoneyAccount reference to account to be re-parented
+ * @param parent MyMoneyAccount reference to new parent account
+ */
+ virtual void reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent);
+
+ /**
+ * This method is used to remove a transaction from the transaction
+ * pool (journal).
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param transaction const reference to transaction to be deleted
+ */
+ virtual void removeTransaction(const MyMoneyTransaction& transaction);
+
+ /**
+ * This method returns the number of transactions currently known to file
+ * in the range 0..MAXUINT
+ *
+ * @param account QString reference to account id. If account is empty
+ + all transactions (the journal) will be counted. If account
+ * is not empty it returns the number of transactions
+ * that have splits in this account.
+ *
+ * @return number of transactions in journal/account
+ */
+ virtual unsigned int transactionCount(const QString& account = QString()) const;
+
+ /**
+ * This method returns a QMap filled with the number of transactions
+ * per account. The account id serves as index into the map. If one
+ * needs to have all transactionCounts() for many accounts, this method
+ * is faster than calling transactionCount(const QString& account) many
+ * times.
+ *
+ * @return QMap with numbers of transactions per account
+ */
+ virtual const QMap<QString, unsigned long> transactionCountMap(void) const;
+
+ /**
+ * This method is used to pull a list of transactions from the file
+ * global transaction pool. It returns all those transactions
+ * that match the filter passed as argument. If the filter is empty,
+ * the whole journal will be returned.
+ * The list returned is sorted according to the transactions posting date.
+ * If more than one transaction exists for the same date, the order among
+ * them is undefined.
+ *
+ * @param filter MyMoneyTransactionFilter object with the match criteria
+ *
+ * @return set of transactions in form of a QValueList<MyMoneyTransaction>
+ */
+ virtual const QValueList<MyMoneyTransaction> transactionList(MyMoneyTransactionFilter& filter) const;
+
+ /**
+ * This method is the same as above, but instead of a return value, a
+ * parameter is used.
+ *
+ * @param list The set of transactions returned. The list passed in will
+ * be cleared before filling with results.
+ * @param filter MyMoneyTransactionFilter object with the match criteria
+ */
+ virtual void transactionList(QValueList<MyMoneyTransaction>& list, MyMoneyTransactionFilter& filter) const;
+
+ /**
+ * This method is the same as above, but the list contains pairs of
+ * transactions and splits.
+ *
+ * @param list The set of transactions returned. The list passed in will
+ * be cleared before filling with results.
+ * @param filter MyMoneyTransactionFilter object with the match criteria
+ */
+ virtual void transactionList(QValueList<QPair<MyMoneyTransaction, MyMoneySplit> >& list, MyMoneyTransactionFilter& filter) const;
+
+ /**
+ * Deletes an existing account from the file global account pool
+ * This method only allows to remove accounts that are not
+ * referenced by any split. Use moveSplits() to move splits
+ * to another account. An exception is thrown in case of a
+ * problem.
+ *
+ * @param account reference to the account to be deleted.
+ */
+ virtual void removeAccount(const MyMoneyAccount& account);
+
+ /**
+ * Deletes an existing institution from the file global institution pool
+ * Also modifies the accounts that reference this institution as
+ * their institution.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param institution institution to be deleted.
+ */
+ virtual void removeInstitution(const MyMoneyInstitution& institution);
+
+ /**
+ * This method is used to extract a transaction from the file global
+ * transaction pool through an id. In case of an invalid id, an
+ * exception will be thrown.
+ *
+ * @param id id of transaction as QString.
+ * @return the requested transaction
+ */
+ virtual const MyMoneyTransaction transaction(const QString& id) const;
+
+ /**
+ * This method is used to extract a transaction from the file global
+ * transaction pool through an index into an account.
+ *
+ * @param account id of the account as QString
+ * @param idx number of transaction in this account
+ * @return MyMoneyTransaction object
+ */
+ virtual const MyMoneyTransaction transaction(const QString& account, const int idx) const;
+
+ /**
+ * This method returns the number of institutions currently known to file
+ * in the range 0..MAXUINT
+ *
+ * @return number of institutions known to file
+ */
+ virtual unsigned int institutionCount(void) const;
+
+ /**
+ * This method returns a list of accounts inside the storage object.
+ *
+ * @param list reference to QValueList receiving the account objects
+ *
+ * @note The standard accounts will not be returned
+ */
+ virtual void accountList(QValueList<MyMoneyAccount>& list) const;
+
+ /**
+ * This method is used to return the standard liability account
+ * @return MyMoneyAccount liability account(group)
+ */
+ virtual const MyMoneyAccount liability(void) const;
+
+ /**
+ * This method is used to return the standard asset account
+ * @return MyMoneyAccount asset account(group)
+ */
+ virtual const MyMoneyAccount asset(void) const;
+
+ /**
+ * This method is used to return the standard expense account
+ * @return MyMoneyAccount expense account(group)
+ */
+ virtual const MyMoneyAccount expense(void) const;
+
+ /**
+ * This method is used to return the standard income account
+ * @return MyMoneyAccount income account(group)
+ */
+ virtual const MyMoneyAccount income(void) const;
+
+ /**
+ * This method is used to return the standard equity account
+ * @return MyMoneyAccount equity account(group)
+ */
+ virtual const MyMoneyAccount equity(void) const;
+
+ /**
+ * This method is used to create a new security object. The ID will be
+ * created automatically. The object passed with the parameter @p security
+ * is modified to contain the assigned id.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param security MyMoneySecurity filled with data
+ */
+ virtual void addSecurity(MyMoneySecurity& security);
+
+ /**
+ * This method is used to modify an existing MyMoneySecurity
+ * object.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param security reference to the MyMoneySecurity object to be updated
+ */
+ virtual void modifySecurity(const MyMoneySecurity& security);
+
+ /**
+ * This method is used to remove an existing MyMoneySecurity object
+ * from the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param security reference to the MyMoneySecurity object to be removed
+ */
+ virtual void removeSecurity(const MyMoneySecurity& security);
+
+ /**
+ * This method is used to retrieve a single MyMoneySecurity object.
+ * The id of the object must be supplied in the parameter @p id.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param id QString containing the id of the MyMoneySecurity object
+ * @return MyMoneySecurity object
+ */
+ virtual const MyMoneySecurity security(const QString& id) const;
+
+ /**
+ * This method returns a list of the security objects
+ * inside a MyMoneyStorage object
+ *
+ * @return QValueList<MyMoneySecurity> containing objects
+ */
+ virtual const QValueList<MyMoneySecurity> securityList(void) const;
+
+ virtual void addPrice(const MyMoneyPrice& price);
+ virtual void removePrice(const MyMoneyPrice& price);
+ virtual const MyMoneyPrice price(const QString& fromId, const QString& toId, const QDate& date, const bool exactDate) const;
+
+ /**
+ * This method returns a list of all prices.
+ *
+ * @return MyMoneyPriceList of all MyMoneyPrice objects.
+ */
+ virtual const MyMoneyPriceList priceList(void) const;
+
+ /**
+ * This method is used to add a scheduled transaction to the engine.
+ * It must be sure, that the id of the object is not filled. When the
+ * method returns to the caller, the id will be filled with the
+ * newly created object id value.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param sched reference to the MyMoneySchedule object
+ */
+ virtual void addSchedule(MyMoneySchedule& sched);
+
+ /**
+ * This method is used to modify an existing MyMoneySchedule
+ * object. Therefor, the id attribute of the object must be set.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param sched const reference to the MyMoneySchedule object to be updated
+ */
+ virtual void modifySchedule(const MyMoneySchedule& sched);
+
+ /**
+ * This method is used to remove an existing MyMoneySchedule object
+ * from the engine. The id attribute of the object must be set.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param sched const reference to the MyMoneySchedule object to be updated
+ */
+ virtual void removeSchedule(const MyMoneySchedule& sched);
+
+ /**
+ * This method is used to retrieve a single MyMoneySchedule object.
+ * The id of the object must be supplied in the parameter @p id.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param id QString containing the id of the MyMoneySchedule object
+ * @return MyMoneySchedule object
+ */
+ virtual const MyMoneySchedule schedule(const QString& id) const;
+
+ /**
+ * This method is used to extract a list of scheduled transactions
+ * according to the filter criteria passed as arguments.
+ *
+ * @param accountId only search for scheduled transactions that reference
+ * accound @p accountId. If accountId is the empty string,
+ * this filter is off. Default is @p QString().
+ * @param type only schedules of type @p type are searched for.
+ * See MyMoneySchedule::typeE for details.
+ * Default is MyMoneySchedule::TYPE_ANY
+ * @param occurence only schedules of occurence type @p occurance are searched for.
+ * See MyMoneySchedule::occurenceE for details.
+ * Default is MyMoneySchedule::OCCUR_ANY
+ * @param paymentType only schedules of payment method @p paymentType
+ * are searched for.
+ * See MyMoneySchedule::paymentTypeE for details.
+ * Default is MyMoneySchedule::STYPE_ANY
+ * @param startDate only schedules with payment dates after @p startDate
+ * are searched for. Default is all dates (QDate()).
+ * @param endDate only schedules with payment dates ending prior to @p endDate
+ * are searched for. Default is all dates (QDate()).
+ * @param overdue if true, only those schedules that are overdue are
+ * searched for. Default is false (all schedules will be returned).
+ *
+ * @return const QValueList<MyMoneySchedule> list of schedule objects.
+ */
+ virtual const QValueList<MyMoneySchedule> scheduleList(const QString& accountId = QString(),
+ const MyMoneySchedule::typeE type = MyMoneySchedule::TYPE_ANY,
+ const MyMoneySchedule::occurenceE occurence = MyMoneySchedule::OCCUR_ANY,
+ const MyMoneySchedule::paymentTypeE paymentType = MyMoneySchedule::STYPE_ANY,
+ const QDate& startDate = QDate(),
+ const QDate& endDate = QDate(),
+ const bool overdue = false) const;
+
+ virtual const QValueList<MyMoneySchedule> scheduleListEx( int scheduleTypes,
+ int scheduleOcurrences,
+ int schedulePaymentTypes,
+ QDate startDate,
+ const QStringList& accounts=QStringList()) const;
+
+ /**
+ * This method is used to add a new currency object to the engine.
+ * The ID of the object is the trading symbol, so there is no need for an additional
+ * ID since the symbol is guaranteed to be unique.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param currency reference to the MyMoneySecurity object
+ */
+ virtual void addCurrency(const MyMoneySecurity& currency);
+
+ /**
+ * This method is used to modify an existing MyMoneySecurity
+ * object.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param currency reference to the MyMoneyCurrency object
+ */
+ virtual void modifyCurrency(const MyMoneySecurity& currency);
+
+ /**
+ * This method is used to remove an existing MyMoneySecurity object
+ * from the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param currency reference to the MyMoneySecurity object
+ */
+ virtual void removeCurrency(const MyMoneySecurity& currency);
+
+ /**
+ * This method is used to retrieve a single MyMoneySecurity object.
+ * The id of the object must be supplied in the parameter @p id.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param id QString containing the id of the MyMoneySecurity object
+ * @return MyMoneyCurrency object
+ */
+ virtual const MyMoneySecurity currency(const QString& id) const;
+
+ /**
+ * This method is used to retrieve the list of all currencies
+ * known to the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @return QValueList of all MyMoneySecurity objects representing a currency.
+ */
+ virtual const QValueList<MyMoneySecurity> currencyList(void) const;
+
+ /**
+ * This method is used to retrieve the list of all reports
+ * known to the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @return QValueList of all MyMoneyReport objects.
+ */
+ virtual const QValueList<MyMoneyReport> reportList( void ) const;
+
+ /**
+ * This method is used to add a new report to the engine.
+ * It must be sure, that the id of the object is not filled. When the
+ * method returns to the caller, the id will be filled with the
+ * newly created object id value.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param report reference to the MyMoneyReport object
+ */
+ virtual void addReport( MyMoneyReport& report );
+
+ /**
+ * This method is used to modify an existing MyMoneyReport
+ * object. Therefor, the id attribute of the object must be set.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param report const reference to the MyMoneyReport object to be updated
+ */
+ virtual void modifyReport( const MyMoneyReport& report );
+
+ /**
+ * This method returns the number of reports currently known to file
+ * in the range 0..MAXUINT
+ *
+ * @return number of reports known to file
+ */
+ virtual unsigned countReports( void ) const;
+
+ /**
+ * This method is used to retrieve a single MyMoneyReport object.
+ * The id of the object must be supplied in the parameter @p id.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param id QString containing the id of the MyMoneyReport object
+ * @return MyMoneyReport object
+ */
+ virtual const MyMoneyReport report( const QString& id ) const;
+
+ /**
+ * This method is used to remove an existing MyMoneyReport object
+ * from the engine. The id attribute of the object must be set.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param report const reference to the MyMoneyReport object to be updated
+ */
+ virtual void removeReport(const MyMoneyReport& report);
+
+ /**
+ * This method is used to retrieve the list of all budgets
+ * known to the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @return QValueList of all MyMoneyBudget objects.
+ */
+ virtual const QValueList<MyMoneyBudget> budgetList( void ) const;
+
+ /**
+ * This method is used to add a new budget to the engine.
+ * It must be sure, that the id of the object is not filled. When the
+ * method returns to the caller, the id will be filled with the
+ * newly created object id value.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param budget reference to the MyMoneyBudget object
+ */
+ virtual void addBudget( MyMoneyBudget& budget );
+
+ /**
+ * This method is used to retrieve the id to a corresponding
+ * name of a budget
+ * An exception will be thrown upon error conditions.
+ *
+ * @param budget QString reference to name of budget
+ *
+ * @return MyMoneyBudget object of budget
+ */
+ virtual const MyMoneyBudget budgetByName(const QString& budget) const;
+
+ /**
+ * This method is used to modify an existing MyMoneyBudget
+ * object. Therefor, the id attribute of the object must be set.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param budget const reference to the MyMoneyBudget object to be updated
+ */
+ virtual void modifyBudget( const MyMoneyBudget& budget );
+
+ /**
+ * This method returns the number of budgets currently known to file
+ * in the range 0..MAXUINT
+ *
+ * @return number of budgets known to file
+ */
+ virtual unsigned countBudgets( void ) const;
+
+ /**
+ * This method is used to retrieve a single MyMoneyBudget object.
+ * The id of the object must be supplied in the parameter @p id.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param id QString containing the id of the MyMoneyBudget object
+ * @return MyMoneyBudget object
+ */
+ virtual MyMoneyBudget budget( const QString& id ) const;
+
+ /**
+ * This method is used to remove an existing MyMoneyBudget object
+ * from the engine. The id attribute of the object must be set.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param budget const reference to the MyMoneyBudget object to be updated
+ */
+ virtual void removeBudget(const MyMoneyBudget& budget);
+
+
+
+ /**
+ * Clear all internal caches (used internally for performance measurements)
+ */
+ virtual void clearCache(void);
+
+ /**
+ * This method checks, if the given @p object is referenced
+ * by another engine object.
+ *
+ * @param obj const reference to object to be checked
+ * @param skipCheck MyMoneyFileBitArray with ReferenceCheckBits set for which
+ * the check should be skipped
+ *
+ * @retval false @p object is not referenced
+ * @retval true @p institution is referenced
+ */
+ virtual bool isReferenced(const MyMoneyObject& obj, const MyMoneyFileBitArray& skipCheck = MyMoneyFileBitArray()) const;
+
+ /**
+ * This method is provided to allow closing of the database before logoff
+ */
+ virtual void close(void);
+
+ /**
+ * These methods have to be provided to allow transaction safe data handling.
+ */
+ virtual void startTransaction(void);
+ virtual bool commitTransaction(void);
+ virtual void rollbackTransaction(void);
+
+ // general set functions
+ virtual void setCreationDate(const QDate& val);
+
+ /**
+ * This method is used to get a SQL reader for subsequent database access
+ */
+ virtual KSharedPtr <MyMoneyStorageSql> connectToDatabase
+ (const KURL& url);
+ /**
+ * This method is used when a database file is open, and the data is to
+ * be saved in a different file or format. It will ensure that all data
+ * from the database is available in memory to enable it to be written.
+ */
+ virtual void fillStorage();
+
+ /**
+ * This method is used to set the last modification date of
+ * the storage object. It also clears the dirty flag and should
+ * therefor be called as last operation when loading from a
+ * file.
+ *
+ * @param val QDate of last modification
+ */
+ virtual void setLastModificationDate(const QDate& val);
+
+ /**
+ * This method returns whether a given transaction is already in memory, to avoid
+ * reloading it from the database
+ */
+ virtual bool isDuplicateTransaction(const QString&) const;
+
+ virtual void loadAccounts(const QMap<QString, MyMoneyAccount>& map);
+ virtual void loadTransactions(const QMap<QString, MyMoneyTransaction>& map);
+ virtual void loadInstitutions(const QMap<QString, MyMoneyInstitution>& map);
+ virtual void loadPayees(const QMap<QString, MyMoneyPayee>& map);
+ virtual void loadSchedules(const QMap<QString, MyMoneySchedule>& map);
+ virtual void loadSecurities(const QMap<QString, MyMoneySecurity>& map);
+ virtual void loadCurrencies(const QMap<QString, MyMoneySecurity>& map);
+ virtual void loadReports( const QMap<QString, MyMoneyReport>& reports );
+ virtual void loadBudgets( const QMap<QString, MyMoneyBudget>& budgets );
+ virtual void loadPrices(const MyMoneyPriceList& list);
+
+ virtual unsigned long accountId(void) const;
+ virtual unsigned long transactionId(void) const;
+ virtual unsigned long payeeId(void) const;
+ virtual unsigned long institutionId(void) const;
+ virtual unsigned long scheduleId(void) const;
+ virtual unsigned long securityId(void) const;
+ virtual unsigned long reportId(void) const;
+ virtual unsigned long budgetId(void) const;
+
+ virtual void loadAccountId(const unsigned long id);
+ virtual void loadTransactionId(const unsigned long id);
+ virtual void loadPayeeId(const unsigned long id);
+ virtual void loadInstitutionId(const unsigned long id);
+ virtual void loadScheduleId(const unsigned long id);
+ virtual void loadSecurityId(const unsigned long id);
+ virtual void loadReportId(const unsigned long id);
+ virtual void loadBudgetId(const unsigned long id);
+
+ /**
+ * This method is used to retrieve the whole set of key/value pairs
+ * from the container. It is meant to be used for permanent storage
+ * functionality. See MyMoneyKeyValueContainer::pairs() for details.
+ *
+ * @return QMap<QString, QString> containing all key/value pairs of
+ * this container.
+ */
+ virtual const QMap<QString, QString> pairs(void) const;
+
+ /**
+ * This method is used to initially store a set of key/value pairs
+ * in the container. It is meant to be used for loading functionality
+ * from permanent storage. See MyMoneyKeyValueContainer::setPairs()
+ * for details
+ *
+ * @param list const QMap<QString, QString> containing the set of
+ * key/value pairs to be loaded into the container.
+ *
+ * @note All existing key/value pairs in the container will be deleted.
+ */
+ virtual void setPairs(const QMap<QString, QString>& list);
+
+ /**
+ * This method recalculates the balances of all accounts
+ * based on the transactions stored in the engine.
+ */
+ virtual void rebuildAccountBalances(void);
+
+private:
+ /**
+ * This member variable keeps the creation date of this MyMoneySeqAccessMgr
+ * object. It is set during the constructor and can only be modified using
+ * the stream read operator.
+ */
+ QDate m_creationDate;
+
+ /**
+ * This member variable contains the current fix level of application
+ * data files. (see kmymoneyview.cpp)
+ */
+ unsigned int m_currentFixVersion;
+
+ /**
+ * This member variable contains the current fix level of the
+ * presently open data file. (see kmymoneyview.cpp)
+ */
+ unsigned int m_fileFixVersion;
+
+ /**
+ * This member variable keeps the date of the last modification of
+ * the MyMoneySeqAccessMgr object.
+ */
+ QDate m_lastModificationDate;
+
+ /**
+ * This contains the interface with SQL reader for database access
+ */
+ KSharedPtr <MyMoneyStorageSql> m_sql;
+
+ /**
+ * This member variable keeps the User information.
+ * @see setUser()
+ */
+ MyMoneyPayee m_user;
+
+ /**
+ * This method is used to get the next valid ID for a institution
+ * @return id for a institution
+ */
+ const QString nextInstitutionID(void);
+
+ /**
+ * This method is used to get the next valid ID for an account
+ * @return id for an account
+ */
+ const QString nextAccountID(void);
+
+ /**
+ * This method is used to get the next valid ID for a transaction
+ * @return id for a transaction
+ */
+ const QString nextTransactionID(void);
+
+ /**
+ * This method is used to get the next valid ID for a payee
+ * @return id for a payee
+ */
+ const QString nextPayeeID(void);
+
+ /**
+ * This method is used to get the next valid ID for a scheduled transaction
+ * @return id for a scheduled transaction
+ */
+ const QString nextScheduleID(void);
+
+ /**
+ * This method is used to get the next valid ID for an security object.
+ * @return id for an security object
+ */
+ const QString nextSecurityID(void);
+
+ const QString nextReportID(void);
+
+ /**
+ * This method is used to get the next valid ID for a budget object.
+ * @return id for an budget object
+ */
+ const QString nextBudgetID(void);
+
+ void removeReferences(const QString& id);
+
+ static const int INSTITUTION_ID_SIZE = 6;
+ static const int ACCOUNT_ID_SIZE = 6;
+ static const int TRANSACTION_ID_SIZE = 18;
+ static const int PAYEE_ID_SIZE = 6;
+ static const int SCHEDULE_ID_SIZE = 6;
+ static const int SECURITY_ID_SIZE = 6;
+ static const int REPORT_ID_SIZE = 6;
+ static const int BUDGET_ID_SIZE = 6;
+
+ // Increment this to force an update in KMMView.
+ // This is different from the db schema version stored in
+ // MMStorageSql::m_majorVersion
+ static const int CURRENT_FIX_VERSION = 3;
+
+};
+#endif
diff --git a/kmymoney2/mymoney/storage/mymoneydatabasemgrtest.cpp b/kmymoney2/mymoney/storage/mymoneydatabasemgrtest.cpp
new file mode 100644
index 0000000..f6a2bba
--- /dev/null
+++ b/kmymoney2/mymoney/storage/mymoneydatabasemgrtest.cpp
@@ -0,0 +1,1996 @@
+/***************************************************************************
+ mymoneydatabasemgrtest.cpp
+ -------------------
+ copyright : (C) 2008 by Fernando Vilas
+ email : fvilas@iname.com
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "mymoneydatabasemgrtest.h"
+#include <pwd.h>
+#include <iostream>
+
+MyMoneyDatabaseMgrTest::MyMoneyDatabaseMgrTest()
+ : m_dbAttached (false),
+ m_canOpen (true)
+{}
+
+void MyMoneyDatabaseMgrTest::setUp()
+{
+ m = new MyMoneyDatabaseMgr;
+
+ m->startTransaction();
+}
+
+void MyMoneyDatabaseMgrTest::tearDown()
+{
+ if (m_canOpen) {
+ m->commitTransaction();
+ }
+ if (MyMoneyFile::instance()->storageAttached()) {
+ MyMoneyFile::instance()->detachStorage(m);
+ }
+ delete m;
+}
+
+void MyMoneyDatabaseMgrTest::testEmptyConstructor()
+{
+ MyMoneyPayee user = m->user();
+
+ CPPUNIT_ASSERT(user.name().isEmpty());
+ CPPUNIT_ASSERT(user.address().isEmpty());
+ CPPUNIT_ASSERT(user.city().isEmpty());
+ CPPUNIT_ASSERT(user.state().isEmpty());
+ CPPUNIT_ASSERT(user.postcode().isEmpty());
+ CPPUNIT_ASSERT(user.telephone().isEmpty());
+ CPPUNIT_ASSERT(user.email().isEmpty());
+ CPPUNIT_ASSERT(m->nextInstitutionID() == 0);
+ CPPUNIT_ASSERT(m->nextAccountID() == 0);
+ CPPUNIT_ASSERT(m->nextTransactionID() == 0);
+ CPPUNIT_ASSERT(m->nextPayeeID() == 0);
+ CPPUNIT_ASSERT(m->nextScheduleID() == 0);
+ CPPUNIT_ASSERT(m->nextReportID() == 0);
+ CPPUNIT_ASSERT(m->institutionList().count() == 0);
+
+ QValueList<MyMoneyAccount> accList;
+ m->accountList(accList);
+ CPPUNIT_ASSERT(accList.count() == 0);
+
+ MyMoneyTransactionFilter f;
+ CPPUNIT_ASSERT(m->transactionList(f).count() == 0);
+
+ CPPUNIT_ASSERT(m->payeeList().count() == 0);
+ CPPUNIT_ASSERT(m->scheduleList().count() == 0);
+
+ CPPUNIT_ASSERT(m->m_creationDate == QDate::currentDate());
+}
+
+void MyMoneyDatabaseMgrTest::testCreateDb() {
+ m->commitTransaction();
+
+ // Fetch the list of available drivers
+ QStringList list = QSqlDatabase::drivers();
+ QStringList::Iterator it = list.begin();
+
+ if (it == list.end()) {
+ m_canOpen = false;
+ } else {
+ struct passwd * pwd = getpwuid(geteuid());
+ QString userName;
+ if (pwd != 0) {
+ userName = QString(pwd->pw_name);
+ }
+ //"QMYSQL3"
+ //"QPSQL7"
+ //"QSQLITE3"
+ m_url = "sql://" + userName + "@localhost/kmm_test_driver?driver="
+ //"QPSQL7&mode=single";
+ //"QSQLITE3&mode=single";
+ //"QMYSQL3&mode=single";
+ + *it + "&mode=single";
+ KSharedPtr <MyMoneyStorageSql> sql = m->connectToDatabase(m_url);
+ CPPUNIT_ASSERT(0 != sql);
+ //qDebug("Database driver is %s", sql->driverName().ascii());
+ // Clear the database, so there is a fresh start on each run.
+ if (0 == sql->open(m_url, IO_WriteOnly, true)) {
+ MyMoneyFile::instance()->attachStorage(m);
+ CPPUNIT_ASSERT(sql->writeFile());
+ m->startTransaction();
+ CPPUNIT_ASSERT(0 == sql->upgradeDb());
+ } else {
+ m_canOpen = false;
+ }
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testAttachDb() {
+ if (!m_dbAttached) {
+ testCreateDb();
+ if (m_canOpen) {
+ MyMoneyFile::instance()->detachStorage();
+ KSharedPtr <MyMoneyStorageSql> sql = m->connectToDatabase(m_url);
+ CPPUNIT_ASSERT(sql);
+ int openStatus = sql->open(m_url, IO_ReadWrite);
+ CPPUNIT_ASSERT(0 == openStatus);
+ MyMoneyFile::instance()->attachStorage(m);
+ m->startTransaction();
+ m_dbAttached = true;
+ }
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testSetFunctions() {
+ testAttachDb();
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ MyMoneyPayee user = m->user();
+
+ user.setName("Name");
+ m->setUser(user);
+ user.setAddress("Street");
+ m->setUser(user);
+ user.setCity("Town");
+ m->setUser(user);
+ user.setState("County");
+ m->setUser(user);
+ user.setPostcode("Postcode");
+ m->setUser(user);
+ user.setTelephone("Telephone");
+ m->setUser(user);
+ user.setEmail("Email");
+ m->setUser(user);
+ m->setValue("key", "value");
+
+ user = m->user();
+ CPPUNIT_ASSERT(user.name() == "Name");
+ CPPUNIT_ASSERT(user.address() == "Street");
+ CPPUNIT_ASSERT(user.city() == "Town");
+ CPPUNIT_ASSERT(user.state() == "County");
+ CPPUNIT_ASSERT(user.postcode() == "Postcode");
+ CPPUNIT_ASSERT(user.telephone() == "Telephone");
+ CPPUNIT_ASSERT(user.email() == "Email");
+ CPPUNIT_ASSERT(m->value("key") == "value");
+
+ m->setDirty();
+ m->deletePair("key");
+ CPPUNIT_ASSERT(m->dirty() == false);
+}
+
+void MyMoneyDatabaseMgrTest::testSupportFunctions()
+{
+ testAttachDb();
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ CPPUNIT_ASSERT(m->nextInstitutionID() == "I000001");
+ CPPUNIT_ASSERT(m->nextAccountID() == "A000001");
+ CPPUNIT_ASSERT(m->nextTransactionID() == "T000000000000000001");
+ CPPUNIT_ASSERT(m->nextPayeeID() == "P000001");
+ CPPUNIT_ASSERT(m->nextScheduleID() == "SCH000001");
+ CPPUNIT_ASSERT(m->nextReportID() == "R000001");
+
+ CPPUNIT_ASSERT(m->liability().name() == "Liability");
+ CPPUNIT_ASSERT(m->asset().name() == "Asset");
+ CPPUNIT_ASSERT(m->expense().name() == "Expense");
+ CPPUNIT_ASSERT(m->income().name() == "Income");
+ CPPUNIT_ASSERT(m->equity().name() == "Equity");
+ CPPUNIT_ASSERT(m->dirty() == false);
+}
+
+void MyMoneyDatabaseMgrTest::testIsStandardAccount()
+{
+ testAttachDb();
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ CPPUNIT_ASSERT(m->isStandardAccount(STD_ACC_LIABILITY) == true);
+ CPPUNIT_ASSERT(m->isStandardAccount(STD_ACC_ASSET) == true);
+ CPPUNIT_ASSERT(m->isStandardAccount(STD_ACC_EXPENSE) == true);
+ CPPUNIT_ASSERT(m->isStandardAccount(STD_ACC_INCOME) == true);
+ CPPUNIT_ASSERT(m->isStandardAccount(STD_ACC_EQUITY) == true);
+ CPPUNIT_ASSERT(m->isStandardAccount("A0002") == false);
+}
+
+void MyMoneyDatabaseMgrTest::testNewAccount() {
+ testAttachDb();
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ MyMoneyAccount a;
+
+ a.setName("AccountName");
+ a.setNumber("AccountNumber");
+
+ m->addAccount(a);
+
+ CPPUNIT_ASSERT(m->accountId() == 1);
+ QValueList<MyMoneyAccount> accList;
+ m->accountList(accList);
+ CPPUNIT_ASSERT(accList.count() == 1);
+ CPPUNIT_ASSERT((*(accList.begin())).name() == "AccountName");
+ CPPUNIT_ASSERT((*(accList.begin())).id() == "A000001");
+}
+
+void MyMoneyDatabaseMgrTest::testAccount() {
+ testNewAccount();
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ m->setDirty();
+
+ MyMoneyAccount a;
+
+ // make sure that an invalid ID causes an exception
+ try {
+ a = m->account("Unknown ID");
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+ CPPUNIT_ASSERT(m->dirty() == false);
+
+ // now make sure, that a real ID works
+ try {
+ a = m->account("A000001");
+ CPPUNIT_ASSERT(a.name() == "AccountName");
+ CPPUNIT_ASSERT(a.id() == "A000001");
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testAddNewAccount() {
+ testNewAccount();
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ MyMoneyAccount a,b;
+ b.setName("Account2");
+ b.setNumber("Acc2");
+ m->addAccount(b);
+
+ m->setDirty();
+
+ CPPUNIT_ASSERT(m->accountId() == 2);
+ QValueList<MyMoneyAccount> accList;
+ m->accountList(accList);
+ CPPUNIT_ASSERT(accList.count() == 2);
+
+ // try to add account to undefined account
+ try {
+ MyMoneyAccount c("UnknownID", b);
+ m->addAccount(c, a);
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ CPPUNIT_ASSERT(m->dirty() == false);
+ // now try to add account 1 as sub-account to account 2
+ a = m->account("A000001");
+ try {
+ CPPUNIT_ASSERT(m->asset().accountList().count() == 0);
+ m->addAccount(b, a);
+ MyMoneyAccount acc (m->account("A000002"));
+ CPPUNIT_ASSERT(acc.accountList()[0] == "A000001");
+ CPPUNIT_ASSERT(acc.accountList().count() == 1);
+ CPPUNIT_ASSERT(m->asset().accountList().count() == 0);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testAddInstitution() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ MyMoneyInstitution i;
+
+ i.setName("Inst Name");
+
+ m->addInstitution(i);
+ CPPUNIT_ASSERT(m->institutionList().count() == 1);
+ CPPUNIT_ASSERT(m->institutionId() == 1);
+ CPPUNIT_ASSERT((*(m->institutionList().begin())).name() == "Inst Name");
+ CPPUNIT_ASSERT((*(m->institutionList().begin())).id() == "I000001");
+}
+
+void MyMoneyDatabaseMgrTest::testInstitution() {
+ testAddInstitution();
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ MyMoneyInstitution i;
+
+ m->setDirty();
+
+ // try to find unknown institution
+ try {
+ i = m->institution("Unknown ID");
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ CPPUNIT_ASSERT(m->dirty() == false);
+
+ // now try to find real institution
+ try {
+ i = m->institution("I000001");
+ CPPUNIT_ASSERT(i.name() == "Inst Name");
+ CPPUNIT_ASSERT(m->dirty() == false);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testAccount2Institution() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ testAddInstitution();
+ testAddNewAccount();
+
+ MyMoneyInstitution i;
+ MyMoneyAccount a, b;
+
+ try {
+ i = m->institution("I000001");
+ a = m->account("A000001");
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ m->setDirty();
+
+ // try to add to a false institution
+ MyMoneyInstitution fake("Unknown ID", i);
+ a.setInstitutionId(fake.id());
+ try {
+ m->modifyAccount(a);
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ CPPUNIT_ASSERT(m->dirty() == false);
+ // now try to do it with a real institution
+ try {
+ CPPUNIT_ASSERT(i.accountList().count() == 0);
+ a.setInstitutionId(i.id());
+ m->modifyAccount(a);
+ CPPUNIT_ASSERT(a.institutionId() == i.id());
+ b = m->account("A000001");
+ CPPUNIT_ASSERT(b.institutionId() == i.id());
+ CPPUNIT_ASSERT(i.accountList().count() == 0);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testModifyAccount() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ testAccount2Institution();
+
+ // test the OK case
+ MyMoneyAccount a = m->account("A000001");
+ a.setName("New account name");
+ m->setDirty();
+ try {
+ m->modifyAccount(a);
+ MyMoneyAccount b = m->account("A000001");
+ CPPUNIT_ASSERT(b.parentAccountId() == a.parentAccountId());
+ CPPUNIT_ASSERT(b.name() == "New account name");
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ // modify institution to unknown id
+ MyMoneyAccount c("Unknown ID", a);
+ m->setDirty();
+ try {
+ m->modifyAccount(c);
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ // use different account type
+ MyMoneyAccount d;
+ d.setAccountType(MyMoneyAccount::CreditCard);
+ MyMoneyAccount f("A000001", d);
+ try {
+ m->modifyAccount(f);
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ // use different parent
+ a.setParentAccountId("A000002");
+ try {
+ m->modifyAccount(c);
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testModifyInstitution() {
+ testAddInstitution();
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ MyMoneyInstitution i = m->institution("I000001");
+
+ m->setDirty();
+ i.setName("New inst name");
+ try {
+ m->modifyInstitution(i);
+ i = m->institution("I000001");
+ CPPUNIT_ASSERT(i.name() == "New inst name");
+
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ // try to modify an institution that does not exist
+ MyMoneyInstitution f("Unknown ID", i);
+ try {
+ m->modifyInstitution(f);
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testReparentAccount() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ // this one adds some accounts to the database
+ MyMoneyAccount ex1;
+ ex1.setAccountType(MyMoneyAccount::Expense);
+ MyMoneyAccount ex2;
+ ex2.setAccountType(MyMoneyAccount::Expense);
+ MyMoneyAccount ex3;
+ ex3.setAccountType(MyMoneyAccount::Expense);
+ MyMoneyAccount ex4;
+ ex4.setAccountType(MyMoneyAccount::Expense);
+ MyMoneyAccount in;
+ in.setAccountType(MyMoneyAccount::Income);
+ MyMoneyAccount ch;
+ ch.setAccountType(MyMoneyAccount::Checkings);
+
+ ex1.setName("Sales Tax");
+ ex2.setName("Sales Tax 16%");
+ ex3.setName("Sales Tax 7%");
+ ex4.setName("Grosseries");
+
+ in.setName("Salary");
+ ch.setName("My checkings account");
+
+ try {
+ m->addAccount(ex1);
+ m->addAccount(ex2);
+ m->addAccount(ex3);
+ m->addAccount(ex4);
+ m->addAccount(in);
+ m->addAccount(ch);
+
+ CPPUNIT_ASSERT(ex1.id() == "A000001");
+ CPPUNIT_ASSERT(ex2.id() == "A000002");
+ CPPUNIT_ASSERT(ex3.id() == "A000003");
+ CPPUNIT_ASSERT(ex4.id() == "A000004");
+ CPPUNIT_ASSERT(in.id() == "A000005");
+ CPPUNIT_ASSERT(ch.id() == "A000006");
+
+ MyMoneyAccount parent = m->expense();
+
+ m->addAccount(parent, ex1);
+ m->addAccount(ex1, ex2);
+ m->addAccount(parent, ex3);
+ m->addAccount(parent, ex4);
+
+ parent = m->income();
+ m->addAccount(parent, in);
+
+ parent = m->asset();
+ m->addAccount(parent, ch);
+
+ MyMoneyFile::instance()->preloadCache();
+ CPPUNIT_ASSERT(m->expense().accountCount() == 3);
+ CPPUNIT_ASSERT(m->account(ex1.id()).accountCount() == 1);
+ CPPUNIT_ASSERT(ex3.parentAccountId() == STD_ACC_EXPENSE);
+
+ //for (int i = 0; i < 100; ++i) {
+ m->reparentAccount(ex3, ex1);
+ //}
+ MyMoneyFile::instance()->preloadCache();
+ CPPUNIT_ASSERT(m->expense().accountCount() == 2);
+ CPPUNIT_ASSERT(m->account(ex1.id()).accountCount() == 2);
+ CPPUNIT_ASSERT(ex3.parentAccountId() == ex1.id());
+ } catch (MyMoneyException *e) {
+ std::cout << std::endl << e->what() << std::endl;
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testAddTransactions() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ testReparentAccount();
+
+ MyMoneyAccount ch;
+ MyMoneyTransaction t1, t2;
+ MyMoneySplit s;
+
+ try {
+ // I made some money, great
+ s.setAccountId("A000006"); // Checkings
+ s.setShares(100000);
+ s.setValue(100000);
+ CPPUNIT_ASSERT(s.id().isEmpty());
+ t1.addSplit(s);
+
+ s.setId(QString()); // enable re-usage of split variable
+ s.setAccountId("A000005"); // Salary
+ s.setShares(-100000);
+ s.setValue(-100000);
+ CPPUNIT_ASSERT(s.id().isEmpty());
+ t1.addSplit(s);
+
+ t1.setPostDate(QDate(2002,5,10));
+ } catch (MyMoneyException *e) {
+ unexpectedException(e);
+ }
+
+ m->setDirty();
+ try {
+ m->addTransaction(t1);
+ CPPUNIT_ASSERT(t1.id() == "T000000000000000001");
+ CPPUNIT_ASSERT(t1.splitCount() == 2);
+ CPPUNIT_ASSERT(m->transactionCount() == 1);
+ } catch (MyMoneyException *e) {
+ unexpectedException(e);
+ }
+
+ try {
+ // I spent some money, not so great
+ s.setId(QString()); // enable re-usage of split variable
+ s.setAccountId("A000004"); // Grosseries
+ s.setShares(10000);
+ s.setValue(10000);
+ CPPUNIT_ASSERT(s.id().isEmpty());
+ t2.addSplit(s);
+
+ s.setId(QString()); // enable re-usage of split variable
+ s.setAccountId("A000002"); // 16% sales tax
+ s.setShares(1200);
+ s.setValue(1200);
+ CPPUNIT_ASSERT(s.id().isEmpty());
+ t2.addSplit(s);
+
+ s.setId(QString()); // enable re-usage of split variable
+ s.setAccountId("A000003"); // 7% sales tax
+ s.setShares(400);
+ s.setValue(400);
+ CPPUNIT_ASSERT(s.id().isEmpty());
+ t2.addSplit(s);
+
+ s.setId(QString()); // enable re-usage of split variable
+ s.setAccountId("A000006"); // Checkings account
+ s.setShares(-11600);
+ s.setValue(-11600);
+ CPPUNIT_ASSERT(s.id().isEmpty());
+ t2.addSplit(s);
+
+ t2.setPostDate(QDate(2002,5,9));
+ } catch (MyMoneyException *e) {
+ unexpectedException(e);
+ }
+ m->setDirty();
+ try {
+ m->addTransaction(t2);
+ CPPUNIT_ASSERT(t2.id() == "T000000000000000002");
+ CPPUNIT_ASSERT(t2.splitCount() == 4);
+ CPPUNIT_ASSERT(m->transactionCount() == 2);
+
+ //QMap<QString, QString>::ConstIterator it_k;
+ MyMoneyTransactionFilter f;
+ QValueList<MyMoneyTransaction> transactionList (m->transactionList(f));
+ QValueList<MyMoneyTransaction>::ConstIterator it_t (transactionList.begin());
+
+ //CPPUNIT_ASSERT((*it_k) == "2002-05-10-T000000000000000001");
+ CPPUNIT_ASSERT((*it_t).id() == "T000000000000000002");
+ //++it_k;
+ ++it_t;
+ //CPPUNIT_ASSERT((*it_k) == "2002-05-09-T000000000000000002");
+ CPPUNIT_ASSERT((*it_t).id() == "T000000000000000001");
+ //++it_k;
+ ++it_t;
+ //CPPUNIT_ASSERT(it_k == m->m_transactionKeys.end());
+ CPPUNIT_ASSERT(it_t == transactionList.end());
+
+ ch = m->account("A000006");
+
+ // check that the account's transaction list is updated
+ QValueList<MyMoneyTransaction> list;
+ MyMoneyTransactionFilter filter("A000006");
+ list = m->transactionList(filter);
+ CPPUNIT_ASSERT(list.size() == 2);
+
+ QValueList<MyMoneyTransaction>::ConstIterator it;
+ it = list.begin();
+ CPPUNIT_ASSERT((*it).id() == "T000000000000000002");
+ ++it;
+ CPPUNIT_ASSERT((*it).id() == "T000000000000000001");
+ ++it;
+ CPPUNIT_ASSERT(it == list.end());
+
+ } catch (MyMoneyException *e) {
+ unexpectedException(e);
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testTransactionCount() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ testAddTransactions();
+ CPPUNIT_ASSERT(m->transactionCount("A000001") == 0);
+ CPPUNIT_ASSERT(m->transactionCount("A000002") == 1);
+ CPPUNIT_ASSERT(m->transactionCount("A000003") == 1);
+ CPPUNIT_ASSERT(m->transactionCount("A000004") == 1);
+ CPPUNIT_ASSERT(m->transactionCount("A000005") == 1);
+ CPPUNIT_ASSERT(m->transactionCount("A000006") == 2);
+}
+
+void MyMoneyDatabaseMgrTest::testAddBudget() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ MyMoneyBudget budget;
+
+ budget.setName("TestBudget");
+ budget.setBudgetStart(QDate::currentDate(Qt::LocalTime));
+
+ m->addBudget(budget);
+
+ CPPUNIT_ASSERT(m->budgetList().count() == 1);
+ CPPUNIT_ASSERT(m->budgetId() == 1);
+ MyMoneyBudget newBudget = m->budgetByName("TestBudget");
+
+ CPPUNIT_ASSERT(budget.budgetStart() == newBudget.budgetStart());
+ CPPUNIT_ASSERT(budget.name() == newBudget.name());
+}
+
+void MyMoneyDatabaseMgrTest::testCopyBudget() {
+ testAddBudget();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ try {
+ MyMoneyBudget oldBudget = m->budgetByName("TestBudget");
+ MyMoneyBudget newBudget = oldBudget;
+
+ newBudget.clearId();
+ newBudget.setName(QString("Copy of %1").arg(oldBudget.name()));
+ m->addBudget(newBudget);
+
+ CPPUNIT_ASSERT(m->budgetList().count() == 2);
+ CPPUNIT_ASSERT(m->budgetId() == 2);
+
+ MyMoneyBudget testBudget = m->budgetByName("TestBudget");
+
+ CPPUNIT_ASSERT(oldBudget.budgetStart() == testBudget.budgetStart());
+ CPPUNIT_ASSERT(oldBudget.name() == testBudget.name());
+
+ testBudget = m->budgetByName("Copy of TestBudget");
+
+ CPPUNIT_ASSERT(testBudget.budgetStart() == newBudget.budgetStart());
+ CPPUNIT_ASSERT(testBudget.name() == newBudget.name());
+ } catch (QString& s) {
+ std::cout << "Error in testCopyBudget(): " << s << std::endl;
+ CPPUNIT_ASSERT(false);
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testModifyBudget() {
+ testAddBudget();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ MyMoneyBudget budget = m->budgetByName("TestBudget");
+
+ budget.setBudgetStart(QDate::currentDate(Qt::LocalTime).addDays(-1));
+
+ m->modifyBudget(budget);
+
+ MyMoneyBudget newBudget = m->budgetByName("TestBudget");
+
+ CPPUNIT_ASSERT(budget.id() == newBudget.id());
+ CPPUNIT_ASSERT(budget.budgetStart() == newBudget.budgetStart());
+ CPPUNIT_ASSERT(budget.name() == newBudget.name());
+}
+
+void MyMoneyDatabaseMgrTest::testRemoveBudget() {
+ testAddBudget();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ MyMoneyBudget budget = m->budgetByName("TestBudget");
+ m->removeBudget(budget);
+
+ try {
+ budget = m->budgetByName("TestBudget");
+ // exception should be thrown if budget not found.
+ CPPUNIT_ASSERT(false);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_ASSERT(true);
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testBalance() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ testAddTransactions();
+
+ CPPUNIT_ASSERT(m->balance("A000001", QDate()).isZero());
+ CPPUNIT_ASSERT(m->balance("A000002", QDate()) == MyMoneyMoney(1200));
+ CPPUNIT_ASSERT(m->balance("A000003", QDate()) == MyMoneyMoney(400));
+ //Add a transaction to zero account A000003
+ MyMoneyTransaction t1;
+ MyMoneySplit s;
+
+ s.setAccountId("A000003");
+ s.setShares(-400);
+ s.setValue(-400);
+ CPPUNIT_ASSERT(s.id().isEmpty());
+ t1.addSplit(s);
+
+ s.setId(QString()); // enable re-usage of split variable
+ s.setAccountId("A000002");
+ s.setShares(400);
+ s.setValue(400);
+ CPPUNIT_ASSERT(s.id().isEmpty());
+ t1.addSplit(s);
+
+ t1.setPostDate(QDate(2007,5,10));
+
+ m->addTransaction(t1);
+
+ //qDebug ("Balance of A000003 is 0 = %s", m->balance("A000003", QDate()).toString().ascii());
+ CPPUNIT_ASSERT(m->balance("A000003", QDate()).isZero());
+
+ //qDebug ("Balance of A000001 is 1600 = %s", m->balance("A000001", QDate()).toString().ascii());
+ CPPUNIT_ASSERT(m->totalBalance("A000001", QDate()) == MyMoneyMoney(1600));
+
+ //qDebug ("Balance of A000006 is -11600 = %s", m->balance("A000006", QDate(2002,5,9)).toString().ascii());
+ CPPUNIT_ASSERT(m->balance("A000006", QDate(2002,5,9)) == MyMoneyMoney(-11600));
+
+ //qDebug ("Balance of A000005 is -100000 = %s", m->balance("A000005", QDate(2002,5,10)).toString().ascii());
+ CPPUNIT_ASSERT(m->balance("A000005", QDate(2002,5,10)) == MyMoneyMoney(-100000));
+
+ //qDebug ("Balance of A000006 is 88400 = %s", m->balance("A000006", QDate(2002,5,10)).toString().ascii());
+ CPPUNIT_ASSERT(m->balance("A000006", QDate(2002,5,10)) == MyMoneyMoney(88400));
+}
+
+void MyMoneyDatabaseMgrTest::testModifyTransaction() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ testAddTransactions();
+
+ MyMoneyTransaction t = m->transaction("T000000000000000002");
+ MyMoneySplit s;
+ MyMoneyAccount ch;
+
+ // just modify simple stuff (splits)
+ CPPUNIT_ASSERT(t.splitCount() == 4);
+
+ s = t.splits()[0];
+ s.setShares(11000);
+ s.setValue(11000);
+ t.modifySplit(s);
+
+ CPPUNIT_ASSERT(t.splitCount() == 4);
+ s = t.splits()[3];
+ s.setShares(-12600);
+ s.setValue(-12600);
+ t.modifySplit(s);
+
+ try {
+ CPPUNIT_ASSERT(m->balance("A000004", QDate()) == MyMoneyMoney(10000));
+ CPPUNIT_ASSERT(m->balance("A000006", QDate()) == MyMoneyMoney(100000-11600));
+ CPPUNIT_ASSERT(m->totalBalance("A000001", QDate()) == MyMoneyMoney(1600));
+ m->modifyTransaction(t);
+ CPPUNIT_ASSERT(m->balance("A000004", QDate()) == MyMoneyMoney(11000));
+ CPPUNIT_ASSERT(m->balance("A000006", QDate()) == MyMoneyMoney(100000-12600));
+ CPPUNIT_ASSERT(m->totalBalance("A000001", QDate()) == MyMoneyMoney(1600));
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ // now modify the date
+ t.setPostDate(QDate(2002,5,11));
+ try {
+ m->modifyTransaction(t);
+ CPPUNIT_ASSERT(m->balance("A000004", QDate()) == MyMoneyMoney(11000));
+ CPPUNIT_ASSERT(m->balance("A000006", QDate()) == MyMoneyMoney(100000-12600));
+ CPPUNIT_ASSERT(m->totalBalance("A000001", QDate()) == MyMoneyMoney(1600));
+
+ //QMap<QString, QString>::ConstIterator it_k;
+ MyMoneyTransactionFilter f;
+ QValueList<MyMoneyTransaction> transactionList (m->transactionList(f));
+ QValueList<MyMoneyTransaction>::ConstIterator it_t (transactionList.begin());
+ //it_k = m->m_transactionKeys.begin();
+ //CPPUNIT_ASSERT((*it_k) == "2002-05-10-T000000000000000001");
+ CPPUNIT_ASSERT((*it_t).id() == "T000000000000000001");
+ //++it_k;
+ ++it_t;
+ //CPPUNIT_ASSERT((*it_k) == "2002-05-11-T000000000000000002");
+ CPPUNIT_ASSERT((*it_t).id() == "T000000000000000002");
+ //++it_k;
+ ++it_t;
+ //CPPUNIT_ASSERT(it_k == m->m_transactionKeys.end());
+ CPPUNIT_ASSERT(it_t == transactionList.end());
+
+ ch = m->account("A000006");
+
+ // check that the account's transaction list is updated
+ QValueList<MyMoneyTransaction> list;
+ MyMoneyTransactionFilter filter("A000006");
+ list = m->transactionList(filter);
+ CPPUNIT_ASSERT(list.size() == 2);
+
+ QValueList<MyMoneyTransaction>::ConstIterator it;
+ it = list.begin();
+ CPPUNIT_ASSERT((*it).id() == "T000000000000000001");
+ ++it;
+ CPPUNIT_ASSERT((*it).id() == "T000000000000000002");
+ ++it;
+ CPPUNIT_ASSERT(it == list.end());
+
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+
+void MyMoneyDatabaseMgrTest::testRemoveUnusedAccount() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ testAccount2Institution();
+
+ MyMoneyAccount a = m->account("A000001");
+ MyMoneyInstitution i = m->institution("I000001");
+
+ m->setDirty();
+ // make sure, we cannot remove the standard account groups
+ try {
+ m->removeAccount(m->liability());
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ try {
+ m->removeAccount(m->asset());
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ try {
+ m->removeAccount(m->expense());
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ try {
+ m->removeAccount(m->income());
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ // try to remove the account still attached to the institution
+ try {
+ m->removeAccount(a);
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ // now really remove an account
+
+ try {
+ MyMoneyFile::instance()->preloadCache();
+ i = m->institution("I000001");
+
+ //CPPUNIT_ASSERT(i.accountCount() == 0);
+ CPPUNIT_ASSERT(i.accountCount() == 1);
+ CPPUNIT_ASSERT(m->accountCount() == 7);
+
+ a.setInstitutionId(QString());
+ m->modifyAccount(a);
+ m->removeAccount(a);
+ CPPUNIT_ASSERT(m->accountCount() == 6);
+ i = m->institution("I000001");
+ CPPUNIT_ASSERT(i.accountCount() == 0);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testRemoveUsedAccount() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ testAddTransactions();
+
+ MyMoneyAccount a = m->account("A000006");
+
+ try {
+ m->removeAccount(a);
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testRemoveInstitution() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ testModifyInstitution();
+ testReparentAccount();
+
+ MyMoneyInstitution i;
+ MyMoneyAccount a;
+
+ // assign the checkings account to the institution
+ try {
+ i = m->institution("I000001");
+ a = m->account("A000006");
+ a.setInstitutionId(i.id());
+ m->modifyAccount(a);
+ CPPUNIT_ASSERT(i.accountCount() == 0);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ m->setDirty();
+ // now remove the institution and see if the account survived ;-)
+ try {
+ m->removeInstitution(i);
+ a.setInstitutionId(QString());
+ m->modifyAccount(a);
+ a = m->account("A000006");
+ CPPUNIT_ASSERT(a.institutionId().isEmpty());
+ CPPUNIT_ASSERT(m->institutionCount() == 0);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testRemoveTransaction() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ testAddTransactions();
+
+ MyMoneyTransaction t = m->transaction("T000000000000000002");
+
+ m->setDirty();
+ try {
+ m->removeTransaction(t);
+ CPPUNIT_ASSERT(m->transactionCount() == 1);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testTransactionList() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ testAddTransactions();
+
+ QValueList<MyMoneyTransaction> list;
+ MyMoneyTransactionFilter filter("A000006");
+ list = m->transactionList(filter);
+ CPPUNIT_ASSERT(list.count() == 2);
+ CPPUNIT_ASSERT((*(list.at(0))).id() == "T000000000000000002");
+ CPPUNIT_ASSERT((*(list.at(1))).id() == "T000000000000000001");
+
+ filter.clear();
+ filter.addAccount(QString("A000003"));
+ list = m->transactionList(filter);
+ CPPUNIT_ASSERT(list.count() == 1);
+ CPPUNIT_ASSERT((*(list.at(0))).id() == "T000000000000000002");
+
+ filter.clear();
+ list = m->transactionList(filter);
+ CPPUNIT_ASSERT(list.count() == 2);
+ CPPUNIT_ASSERT((*(list.at(0))).id() == "T000000000000000002");
+ CPPUNIT_ASSERT((*(list.at(1))).id() == "T000000000000000001");
+}
+
+void MyMoneyDatabaseMgrTest::testAddPayee() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ MyMoneyPayee p;
+
+ p.setName("THB");
+ m->setDirty();
+ try {
+ CPPUNIT_ASSERT(m->payeeId() == 0);
+ m->addPayee(p);
+ CPPUNIT_ASSERT(m->payeeId() == 1);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+}
+
+void MyMoneyDatabaseMgrTest::testSetAccountName() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ try {
+ m->setAccountName(STD_ACC_LIABILITY, "Verbindlichkeiten");
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+ try {
+ m->setAccountName(STD_ACC_ASSET, "Verm�gen");
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+ try {
+ m->setAccountName(STD_ACC_EXPENSE, "Ausgaben");
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+ try {
+ m->setAccountName(STD_ACC_INCOME, "Einnahmen");
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ MyMoneyFile::instance()->preloadCache();
+
+ CPPUNIT_ASSERT(m->liability().name() == "Verbindlichkeiten");
+ CPPUNIT_ASSERT(m->asset().name() == "Verm�gen");
+ CPPUNIT_ASSERT(m->expense().name() == "Ausgaben");
+ CPPUNIT_ASSERT(m->income().name() == "Einnahmen");
+
+ try {
+ m->setAccountName("A000001", "New account name");
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testModifyPayee() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ MyMoneyPayee p;
+
+ testAddPayee();
+
+ p = m->payee("P000001");
+ p.setName("New name");
+ m->setDirty();
+ try {
+ m->modifyPayee(p);
+ p = m->payee("P000001");
+ CPPUNIT_ASSERT(p.name() == "New name");
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testRemovePayee() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ testAddPayee();
+ m->setDirty();
+
+ // check that we can remove an unreferenced payee
+ MyMoneyPayee p = m->payee("P000001");
+ try {
+ CPPUNIT_ASSERT(m->payeeList().count() == 1);
+ m->removePayee(p);
+ CPPUNIT_ASSERT(m->payeeList().count() == 0);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ // add transaction
+ testAddTransactions();
+
+ MyMoneyTransaction tr = m->transaction("T000000000000000001");
+ MyMoneySplit sp;
+ sp = tr.splits()[0];
+ sp.setPayeeId("P000001");
+ tr.modifySplit(sp);
+
+ // check that we cannot add a transaction referencing
+ // an unknown payee
+ try {
+ m->modifyTransaction(tr);
+ CPPUNIT_FAIL("Expected exception");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ // reset here, so that the
+ // testAddPayee will not fail
+ m->loadPayeeId(0);
+ testAddPayee();
+
+ // check that it works when the payee exists
+ try {
+ m->modifyTransaction(tr);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ m->setDirty();
+
+ // now check, that we cannot remove the payee
+ try {
+ m->removePayee(p);
+ CPPUNIT_FAIL("Expected exception");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+ CPPUNIT_ASSERT(m->payeeList().count() == 1);
+}
+
+
+void MyMoneyDatabaseMgrTest::testRemoveAccountFromTree() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ MyMoneyAccount a, b, c;
+ a.setName("Acc A");
+ b.setName("Acc B");
+ c.setName("Acc C");
+
+ // build a tree A -> B -> C, remove B and see if A -> C
+ // remains in the storage manager
+
+ try {
+ m->addAccount(a);
+ m->addAccount(b);
+ m->addAccount(c);
+ m->reparentAccount(b, a);
+ m->reparentAccount(c, b);
+
+ CPPUNIT_ASSERT(a.accountList().count() == 1);
+ CPPUNIT_ASSERT(m->account(a.accountList()[0]).name() == "Acc B");
+
+ CPPUNIT_ASSERT(b.accountList().count() == 1);
+ CPPUNIT_ASSERT(m->account(b.accountList()[0]).name() == "Acc C");
+
+ CPPUNIT_ASSERT(c.accountList().count() == 0);
+
+ m->removeAccount(b);
+
+ // reload account info from titutionIDtorage
+ a = m->account(a.id());
+ c = m->account(c.id());
+
+ try {
+ b = m->account(b.id());
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+ CPPUNIT_ASSERT(a.accountList().count() == 1);
+ CPPUNIT_ASSERT(m->account(a.accountList()[0]).name() == "Acc C");
+
+ CPPUNIT_ASSERT(c.accountList().count() == 0);
+
+ } catch (MyMoneyException *e) {
+ unexpectedException(e);
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testPayeeName() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ testAddPayee();
+
+ MyMoneyPayee p;
+ QString name("THB");
+
+ // OK case
+ try {
+ p = m->payeeByName(name);
+ CPPUNIT_ASSERT(p.name() == "THB");
+ CPPUNIT_ASSERT(p.id() == "P000001");
+ } catch (MyMoneyException *e) {
+ unexpectedException(e);
+ }
+
+ // Not OK case
+ name = "Thb";
+ try {
+ p = m->payeeByName(name);
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testAssignment() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ testAddTransactions();
+
+ MyMoneyPayee user;
+ user.setName("Thomas");
+ m->setUser(user);
+
+ MyMoneyDatabaseMgr test = *m;
+ testEquality(&test);
+}
+
+void MyMoneyDatabaseMgrTest::testEquality(const MyMoneyDatabaseMgr *t)
+{
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ CPPUNIT_ASSERT(m->user().name() == t->user().name());
+ CPPUNIT_ASSERT(m->user().address() == t->user().address());
+ CPPUNIT_ASSERT(m->user().city() == t->user().city());
+ CPPUNIT_ASSERT(m->user().state() == t->user().state());
+ CPPUNIT_ASSERT(m->user().postcode() == t->user().postcode());
+ CPPUNIT_ASSERT(m->user().telephone() == t->user().telephone());
+ CPPUNIT_ASSERT(m->user().email() == t->user().email());
+ //CPPUNIT_ASSERT(m->nextInstitutionID() == t->nextInstitutionID());
+ //CPPUNIT_ASSERT(m->nextAccountID() == t->nextAccountID());
+ //CPPUNIT_ASSERT(m->m_nextTransactionID == t->m_nextTransactionID);
+ //CPPUNIT_ASSERT(m->nextPayeeID() == t->nextPayeeID());
+ //CPPUNIT_ASSERT(m->m_nextScheduleID == t->m_nextScheduleID);
+ CPPUNIT_ASSERT(m->dirty() == t->dirty());
+ CPPUNIT_ASSERT(m->m_creationDate == t->m_creationDate);
+ CPPUNIT_ASSERT(m->m_lastModificationDate == t->m_lastModificationDate);
+
+ /*
+ * make sure, that the keys and values are the same
+ * on the left and the right side
+ */
+ //CPPUNIT_ASSERT(m->payeeList().keys() == t->payeeList().keys());
+ //CPPUNIT_ASSERT(m->payeeList().values() == t->payeeList().values());
+ CPPUNIT_ASSERT(m->payeeList() == t->payeeList());
+ //CPPUNIT_ASSERT(m->m_transactionKeys.keys() == t->m_transactionKeys.keys());
+ //CPPUNIT_ASSERT(m->m_transactionKeys.values() == t->m_transactionKeys.values());
+ //CPPUNIT_ASSERT(m->institutionList().keys() == t->institutionList().keys());
+ //CPPUNIT_ASSERT(m->institutionList().values() == t->institutionList().values());
+ //CPPUNIT_ASSERT(m->m_accountList.keys() == t->m_accountList.keys());
+ //CPPUNIT_ASSERT(m->m_accountList.values() == t->m_accountList.values());
+ //CPPUNIT_ASSERT(m->m_transactionList.keys() == t->m_transactionList.keys());
+ //CPPUNIT_ASSERT(m->m_transactionList.values() == t->m_transactionList.values());
+ //CPPUNIT_ASSERT(m->m_balanceCache.keys() == t->m_balanceCache.keys());
+ //CPPUNIT_ASSERT(m->m_balanceCache.values() == t->m_balanceCache.values());
+
+// CPPUNIT_ASSERT(m->scheduleList().keys() == t->scheduleList().keys());
+// CPPUNIT_ASSERT(m->scheduleList().values() == t->scheduleList().values());
+}
+
+void MyMoneyDatabaseMgrTest::testDuplicate() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ const MyMoneyDatabaseMgr* t;
+
+ testModifyTransaction();
+
+ t = m->duplicate();
+ testEquality(t);
+ delete t;
+}
+
+void MyMoneyDatabaseMgrTest::testAddSchedule() {
+ /* Note addSchedule() now calls validate as it should
+ * so we need an account id. Later this will
+ * be checked to make sure its a valid account id. The
+ * tests currently fail because no splits are defined
+ * for the schedules transaction.
+ */
+
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ // put some accounts in the db, so the tests don't break
+ testReparentAccount();
+
+ try {
+ CPPUNIT_ASSERT(m->scheduleList().count() == 0);
+ MyMoneyTransaction t1;
+ MyMoneySplit s1, s2;
+ s1.setAccountId("A000001");
+ t1.addSplit(s1);
+ s2.setAccountId("A000002");
+ t1.addSplit(s2);
+ MyMoneySchedule schedule("Sched-Name",
+ MyMoneySchedule::TYPE_DEPOSIT,
+ MyMoneySchedule::OCCUR_DAILY, 1,
+ MyMoneySchedule::STYPE_MANUALDEPOSIT,
+ QDate(),
+ QDate(),
+ true,
+ false);
+ t1.setPostDate(QDate(2003,7,10));
+ schedule.setTransaction(t1);
+
+ m->addSchedule(schedule);
+
+ CPPUNIT_ASSERT(m->scheduleList().count() == 1);
+ CPPUNIT_ASSERT(schedule.id() == "SCH000001");
+ MyMoneyFile::instance()->clearCache();
+ CPPUNIT_ASSERT(m->schedule("SCH000001").id() == "SCH000001");
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ try {
+ MyMoneySchedule schedule("Sched-Name",
+ MyMoneySchedule::TYPE_DEPOSIT,
+ MyMoneySchedule::OCCUR_DAILY, 1,
+ MyMoneySchedule::STYPE_MANUALDEPOSIT,
+ QDate(),
+ QDate(),
+ true,
+ false);
+ m->addSchedule(schedule);
+ CPPUNIT_FAIL("Exception expected");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+
+ // now try with a bad account, so this should cause an exception
+ // TODO: enable this check without corrupting other tests
+// try {
+// MyMoneyTransaction t1;
+// MyMoneySplit s1, s2;
+// s1.setAccountId("Abadaccount1");
+// t1.addSplit(s1);
+// s2.setAccountId("Abadaccount2");
+// t1.addSplit(s2);
+// MyMoneySchedule schedule("Sched-Name",
+// MyMoneySchedule::TYPE_DEPOSIT,
+// MyMoneySchedule::OCCUR_DAILY, 1,
+// MyMoneySchedule::STYPE_MANUALDEPOSIT,
+// QDate(),
+// QDate(),
+// true,
+// false);
+// t1.setPostDate(QDate(2003,7,10));
+// schedule.setTransaction(t1);
+
+// m->addSchedule(schedule);
+// CPPUNIT_FAIL("Exception expected, but not thrown");
+// } catch(MyMoneyException *e) {
+// delete e;
+// // Exception caught as expected.
+// }
+
+}
+
+void MyMoneyDatabaseMgrTest::testSchedule() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ testAddSchedule();
+ MyMoneySchedule sched;
+
+ sched = m->schedule("SCH000001");
+ CPPUNIT_ASSERT(sched.name() == "Sched-Name");
+ CPPUNIT_ASSERT(sched.id() == "SCH000001");
+
+ try {
+ m->schedule("SCH000002");
+ CPPUNIT_FAIL("Exception expected");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testModifySchedule() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ testAddSchedule();
+ MyMoneySchedule sched;
+
+ sched = m->schedule("SCH000001");
+ sched.setId("SCH000002");
+ try {
+ m->modifySchedule(sched);
+ CPPUNIT_FAIL("Exception expected");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+
+ sched = m->schedule("SCH000001");
+ sched.setName("New Sched-Name");
+ try {
+ m->modifySchedule(sched);
+ CPPUNIT_ASSERT(m->scheduleList().count() == 1);
+ CPPUNIT_ASSERT((*(m->scheduleList().begin())).name() == "New Sched-Name");
+ CPPUNIT_ASSERT((*(m->scheduleList().begin())).id() == "SCH000001");
+
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+}
+
+void MyMoneyDatabaseMgrTest::testRemoveSchedule() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ testAddSchedule();
+ MyMoneySchedule sched;
+
+ sched = m->schedule("SCH000001");
+ sched.setId("SCH000002");
+ try {
+ m->removeSchedule(sched);
+ CPPUNIT_FAIL("Exception expected");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+
+ sched = m->schedule("SCH000001");
+ try {
+ m->removeSchedule(sched);
+ CPPUNIT_ASSERT(m->scheduleList().count() == 0);
+
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testScheduleList() {
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ // put some accounts in the db, so the tests don't break
+ testReparentAccount();
+
+ QDate testDate = QDate::currentDate();
+ QDate notOverdue = testDate.addDays(2);
+ QDate overdue = testDate.addDays(-2);
+
+ MyMoneyTransaction t1;
+ MyMoneySplit s1, s2;
+ s1.setAccountId("A000001");
+ t1.addSplit(s1);
+ s2.setAccountId("A000002");
+ t1.addSplit(s2);
+ MyMoneySchedule schedule1("Schedule 1",
+ MyMoneySchedule::TYPE_BILL,
+ MyMoneySchedule::OCCUR_ONCE, 1,
+ MyMoneySchedule::STYPE_DIRECTDEBIT,
+ QDate(),
+ QDate(),
+ false,
+ false);
+ t1.setPostDate(notOverdue);
+ schedule1.setTransaction(t1);
+ schedule1.setLastPayment(notOverdue);
+
+ MyMoneyTransaction t2;
+ MyMoneySplit s3, s4;
+ s3.setAccountId("A000001");
+ t2.addSplit(s3);
+ s4.setAccountId("A000003");
+ t2.addSplit(s4);
+ MyMoneySchedule schedule2("Schedule 2",
+ MyMoneySchedule::TYPE_DEPOSIT,
+ MyMoneySchedule::OCCUR_DAILY, 1,
+ MyMoneySchedule::STYPE_DIRECTDEPOSIT,
+ QDate(),
+ QDate(),
+ false,
+ false);
+ t2.setPostDate(notOverdue.addDays(1));
+ schedule2.setTransaction(t2);
+ schedule2.setLastPayment(notOverdue.addDays(1));
+
+ MyMoneyTransaction t3;
+ MyMoneySplit s5, s6;
+ s5.setAccountId("A000005");
+ t3.addSplit(s5);
+ s6.setAccountId("A000006");
+ t3.addSplit(s6);
+ MyMoneySchedule schedule3("Schedule 3",
+ MyMoneySchedule::TYPE_TRANSFER,
+ MyMoneySchedule::OCCUR_WEEKLY, 1,
+ MyMoneySchedule::STYPE_OTHER,
+ QDate(),
+ QDate(),
+ false,
+ false);
+ t3.setPostDate(notOverdue.addDays(2));
+ schedule3.setTransaction(t3);
+ schedule3.setLastPayment(notOverdue.addDays(2));
+
+ MyMoneyTransaction t4;
+ MyMoneySplit s7, s8;
+ s7.setAccountId("A000005");
+ t4.addSplit(s7);
+ s8.setAccountId("A000006");
+ t4.addSplit(s8);
+ MyMoneySchedule schedule4("Schedule 4",
+ MyMoneySchedule::TYPE_BILL,
+ MyMoneySchedule::OCCUR_WEEKLY, 1,
+ MyMoneySchedule::STYPE_WRITECHEQUE,
+ QDate(),
+ notOverdue.addDays(31),
+ false,
+ false);
+ t4.setPostDate(overdue.addDays(-7));
+ schedule4.setTransaction(t4);
+
+ try {
+ m->addSchedule(schedule1);
+ m->addSchedule(schedule2);
+ m->addSchedule(schedule3);
+ m->addSchedule(schedule4);
+ } catch(MyMoneyException *e) {
+ qDebug("Error: %s", e->what().latin1());
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ QValueList<MyMoneySchedule> list;
+
+ // no filter
+ list = m->scheduleList();
+ CPPUNIT_ASSERT(list.count() == 4);
+
+ // filter by type
+ list = m->scheduleList("", MyMoneySchedule::TYPE_BILL);
+ CPPUNIT_ASSERT(list.count() == 2);
+ CPPUNIT_ASSERT(list[0].name() == "Schedule 1");
+ CPPUNIT_ASSERT(list[1].name() == "Schedule 4");
+
+ // filter by occurence
+ list = m->scheduleList("", MyMoneySchedule::TYPE_ANY,
+ MyMoneySchedule::OCCUR_DAILY);
+ CPPUNIT_ASSERT(list.count() == 1);
+ CPPUNIT_ASSERT(list[0].name() == "Schedule 2");
+
+ // filter by payment type
+ list = m->scheduleList("", MyMoneySchedule::TYPE_ANY,
+ MyMoneySchedule::OCCUR_ANY,
+ MyMoneySchedule::STYPE_DIRECTDEPOSIT);
+ CPPUNIT_ASSERT(list.count() == 1);
+ CPPUNIT_ASSERT(list[0].name() == "Schedule 2");
+
+ // filter by account
+ list = m->scheduleList("A01");
+ CPPUNIT_ASSERT(list.count() == 0);
+ list = m->scheduleList("A000001");
+ CPPUNIT_ASSERT(list.count() == 2);
+ list = m->scheduleList("A000002");
+ CPPUNIT_ASSERT(list.count() == 1);
+
+ // filter by start date
+ list = m->scheduleList("", MyMoneySchedule::TYPE_ANY,
+ MyMoneySchedule::OCCUR_ANY,
+ MyMoneySchedule::STYPE_ANY,
+ notOverdue.addDays(31));
+ CPPUNIT_ASSERT(list.count() == 3);
+ CPPUNIT_ASSERT(list[0].name() == "Schedule 2");
+ CPPUNIT_ASSERT(list[1].name() == "Schedule 3");
+ CPPUNIT_ASSERT(list[2].name() == "Schedule 4");
+
+ // filter by end date
+ list = m->scheduleList("", MyMoneySchedule::TYPE_ANY,
+ MyMoneySchedule::OCCUR_ANY,
+ MyMoneySchedule::STYPE_ANY,
+ QDate(),
+ notOverdue.addDays(1));
+ CPPUNIT_ASSERT(list.count() == 3);
+ CPPUNIT_ASSERT(list[0].name() == "Schedule 1");
+ CPPUNIT_ASSERT(list[1].name() == "Schedule 2");
+ CPPUNIT_ASSERT(list[2].name() == "Schedule 4");
+
+ // filter by start and end date
+ list = m->scheduleList("", MyMoneySchedule::TYPE_ANY,
+ MyMoneySchedule::OCCUR_ANY,
+ MyMoneySchedule::STYPE_ANY,
+ notOverdue.addDays(-1),
+ notOverdue.addDays(1));
+ CPPUNIT_ASSERT(list.count() == 2);
+ CPPUNIT_ASSERT(list[0].name() == "Schedule 1");
+ CPPUNIT_ASSERT(list[1].name() == "Schedule 2");
+
+ // filter by overdue status
+ list = m->scheduleList("", MyMoneySchedule::TYPE_ANY,
+ MyMoneySchedule::OCCUR_ANY,
+ MyMoneySchedule::STYPE_ANY,
+ QDate(),
+ QDate(),
+ true);
+ CPPUNIT_ASSERT(list.count() == 1);
+ CPPUNIT_ASSERT(list[0].name() == "Schedule 4");
+}
+
+void MyMoneyDatabaseMgrTest::testAddCurrency()
+{
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ MyMoneySecurity curr("EUR", "Euro", "?", 100, 100);
+ CPPUNIT_ASSERT(m->currencyList().count() == 0);
+ m->setDirty();
+ try {
+ m->addCurrency(curr);
+ CPPUNIT_ASSERT(m->currencyList().count() == 1);
+ CPPUNIT_ASSERT((*(m->currencyList().begin())).name() == "Euro");
+ CPPUNIT_ASSERT((*(m->currencyList().begin())).id() == "EUR");
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ m->setDirty();
+ try {
+ m->addCurrency(curr);
+ CPPUNIT_FAIL("Expected exception missing");
+ } catch(MyMoneyException *e) {
+ CPPUNIT_ASSERT(m->dirty() == false);
+ delete e;
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testModifyCurrency()
+{
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ MyMoneySecurity curr("EUR", "Euro", "?", 100, 100);
+ testAddCurrency();
+ m->setDirty();
+ curr.setName("EURO");
+ try {
+ m->modifyCurrency(curr);
+ CPPUNIT_ASSERT(m->currencyList().count() == 1);
+ CPPUNIT_ASSERT((*(m->currencyList().begin())).name() == "EURO");
+ CPPUNIT_ASSERT((*(m->currencyList().begin())).id() == "EUR");
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ m->setDirty();
+
+ MyMoneySecurity unknownCurr("DEM", "Deutsche Mark", "DM", 100, 100);
+ try {
+ m->modifyCurrency(unknownCurr);
+ CPPUNIT_FAIL("Expected exception missing");
+ } catch(MyMoneyException *e) {
+ CPPUNIT_ASSERT(m->dirty() == false);
+ delete e;
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testRemoveCurrency()
+{
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ MyMoneySecurity curr("EUR", "Euro", "?", 100, 100);
+ testAddCurrency();
+ m->setDirty();
+ try {
+ m->removeCurrency(curr);
+ CPPUNIT_ASSERT(m->currencyList().count() == 0);
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ m->setDirty();
+
+ MyMoneySecurity unknownCurr("DEM", "Deutsche Mark", "DM", 100, 100);
+ try {
+ m->removeCurrency(unknownCurr);
+ CPPUNIT_FAIL("Expected exception missing");
+ } catch(MyMoneyException *e) {
+ CPPUNIT_ASSERT(m->dirty() == false);
+ delete e;
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testCurrency()
+{
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ MyMoneySecurity curr("EUR", "Euro", "?", 100, 100);
+ MyMoneySecurity newCurr;
+ testAddCurrency();
+ m->setDirty();
+ try {
+ newCurr = m->currency("EUR");
+ CPPUNIT_ASSERT(m->dirty() == false);
+ CPPUNIT_ASSERT(newCurr.id() == curr.id());
+ CPPUNIT_ASSERT(newCurr.name() == curr.name());
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ try {
+ m->currency("DEM");
+ CPPUNIT_FAIL("Expected exception missing");
+ } catch(MyMoneyException *e) {
+ CPPUNIT_ASSERT(m->dirty() == false);
+ delete e;
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testCurrencyList()
+{
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ CPPUNIT_ASSERT(m->currencyList().count() == 0);
+
+ testAddCurrency();
+ CPPUNIT_ASSERT(m->currencyList().count() == 1);
+
+ MyMoneySecurity unknownCurr("DEM", "Deutsche Mark", "DM", 100, 100);
+ try {
+ m->addCurrency(unknownCurr);
+ m->setDirty();
+ CPPUNIT_ASSERT(m->currencyList().count() == 2);
+ CPPUNIT_ASSERT(m->currencyList().count() == 2);
+ CPPUNIT_ASSERT(m->dirty() == false);
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneyDatabaseMgrTest::testAccountList()
+{
+ testAttachDb();
+
+ if (!m_canOpen) {
+ std::cout << "Database test skipped because no database could be opened." << std::endl;
+ return;
+ }
+
+ QValueList<MyMoneyAccount> accounts;
+ m->accountList(accounts);
+ CPPUNIT_ASSERT(accounts.count() == 0);
+ testAddNewAccount();
+ accounts.clear();
+ m->accountList(accounts);
+ CPPUNIT_ASSERT(accounts.count() == 2);
+
+ MyMoneyAccount a = m->account("A000001");
+ MyMoneyAccount b = m->account("A000002");
+ m->reparentAccount(b, a);
+ accounts.clear();
+ m->accountList(accounts);
+ CPPUNIT_ASSERT(accounts.count() == 2);
+}
+
diff --git a/kmymoney2/mymoney/storage/mymoneydatabasemgrtest.h b/kmymoney2/mymoney/storage/mymoneydatabasemgrtest.h
new file mode 100644
index 0000000..8ab4b64
--- /dev/null
+++ b/kmymoney2/mymoney/storage/mymoneydatabasemgrtest.h
@@ -0,0 +1,143 @@
+/***************************************************************************
+ mymoneydatabasemgrtest.h
+ -------------------
+ copyright : (C) 2008 by Fernando Vilas
+ email : fvilas@iname.com
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 __MYMONEYDATABASEMGRTEST_H__
+#define __MYMONEYDATABASEMGRTEST_H__
+
+#include <cppunit/TestCaller.h>
+#include <cppunit/TestCase.h>
+#include <cppunit/TestSuite.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include "../autotest.h"
+
+#define private public
+#define protected public
+#include "../mymoneyobject.h"
+#include "mymoneydatabasemgr.h"
+#undef private
+
+class MyMoneyDatabaseMgrTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(MyMoneyDatabaseMgrTest);
+ CPPUNIT_TEST(testEmptyConstructor);
+ CPPUNIT_TEST(testCreateDb);
+ CPPUNIT_TEST(testAttachDb);
+ CPPUNIT_TEST(testSetFunctions);
+ CPPUNIT_TEST(testSupportFunctions);
+ CPPUNIT_TEST(testIsStandardAccount);
+ CPPUNIT_TEST(testNewAccount);
+ CPPUNIT_TEST(testAddNewAccount);
+ CPPUNIT_TEST(testReparentAccount);
+ CPPUNIT_TEST(testAddInstitution);
+ CPPUNIT_TEST(testInstitution);
+ CPPUNIT_TEST(testAccount2Institution);
+ CPPUNIT_TEST(testModifyAccount);
+ CPPUNIT_TEST(testModifyInstitution);
+ CPPUNIT_TEST(testAddTransactions);
+ CPPUNIT_TEST(testTransactionCount);
+ CPPUNIT_TEST(testBalance);
+ CPPUNIT_TEST(testAddBudget);
+ CPPUNIT_TEST(testCopyBudget);
+ CPPUNIT_TEST(testModifyBudget);
+ CPPUNIT_TEST(testRemoveBudget);
+ CPPUNIT_TEST(testModifyTransaction);
+ CPPUNIT_TEST(testRemoveUnusedAccount);
+ CPPUNIT_TEST(testRemoveUsedAccount);
+ CPPUNIT_TEST(testRemoveInstitution);
+ CPPUNIT_TEST(testRemoveTransaction);
+ CPPUNIT_TEST(testTransactionList);
+ CPPUNIT_TEST(testAddPayee);
+ CPPUNIT_TEST(testSetAccountName);
+ CPPUNIT_TEST(testModifyPayee);
+ CPPUNIT_TEST(testPayeeName);
+ CPPUNIT_TEST(testRemovePayee);
+ CPPUNIT_TEST(testRemoveAccountFromTree);
+ CPPUNIT_TEST(testAssignment);
+ CPPUNIT_TEST(testDuplicate);
+ CPPUNIT_TEST(testAddSchedule);
+ CPPUNIT_TEST(testModifySchedule);
+ CPPUNIT_TEST(testRemoveSchedule);
+ CPPUNIT_TEST(testSchedule);
+ CPPUNIT_TEST(testScheduleList);
+ CPPUNIT_TEST(testAddCurrency);
+ CPPUNIT_TEST(testModifyCurrency);
+ CPPUNIT_TEST(testRemoveCurrency);
+ CPPUNIT_TEST(testCurrency);
+ CPPUNIT_TEST(testCurrencyList);
+ CPPUNIT_TEST(testAccountList);
+ CPPUNIT_TEST_SUITE_END();
+
+protected:
+ MyMoneyDatabaseMgr *m;
+ bool m_dbAttached;
+ bool m_canOpen;
+ KURL m_url;
+public:
+ MyMoneyDatabaseMgrTest();
+
+ void setUp();
+ void tearDown();
+ void testEmptyConstructor();
+ void testCreateDb();
+ void testAttachDb();
+ void testSetFunctions();
+ void testIsStandardAccount();
+ void testNewAccount();
+ void testAccount();
+ void testAddNewAccount();
+ void testAddInstitution();
+ void testInstitution();
+ void testAccount2Institution();
+ void testModifyAccount();
+ void testModifyInstitution();
+ void testReparentAccount();
+ void testAddTransactions();
+ void testTransactionCount();
+ void testAddBudget();
+ void testCopyBudget();
+ void testModifyBudget();
+ void testRemoveBudget();
+ void testBalance();
+ void testModifyTransaction();
+ void testRemoveUnusedAccount();
+ void testRemoveUsedAccount();
+ void testRemoveInstitution();
+ void testRemoveTransaction();
+ void testTransactionList();
+ void testAddPayee();
+ void testSetAccountName();
+ void testModifyPayee();
+ void testPayeeName();
+ void testRemovePayee();
+ void testRemoveAccountFromTree();
+ void testAssignment();
+ void testEquality(const MyMoneyDatabaseMgr* t);
+ void testDuplicate();
+ void testAddSchedule();
+ void testSchedule();
+ void testModifySchedule();
+ void testRemoveSchedule();
+ void testSupportFunctions();
+ void testScheduleList();
+ void testAddCurrency();
+ void testModifyCurrency();
+ void testRemoveCurrency();
+ void testCurrency();
+ void testCurrencyList();
+ void testAccountList();
+};
+
+#endif
diff --git a/kmymoney2/mymoney/storage/mymoneymap.h b/kmymoney2/mymoney/storage/mymoneymap.h
new file mode 100644
index 0000000..fa5cda8
--- /dev/null
+++ b/kmymoney2/mymoney/storage/mymoneymap.h
@@ -0,0 +1,328 @@
+#include <stdint.h>
+#include <qmap.h>
+#include <qptrstack.h>
+#include <kmymoney/mymoneyexception.h>
+
+#ifndef MYMONEYMAP_H
+#define MYMONEYMAP_H
+
+#define MY_OWN_DEBUG 0
+
+/**
+ * @author Thomas Baumgart
+ *
+ * This template class adds transaction security to the QMap<> class.
+ * The interface is very simple. Before you perform any changes,
+ * you have to call the startTransaction() method. Then you can use
+ * the insert(), modify() and remove() methods to modify the map.
+ * Changes are recorded and if you are finished, use the
+ * commitTransaction() to finish the transaction. If you want to go
+ * back before you have committed the transaction, use
+ * rollbackTransaction() to set the container to the state it was
+ * in before you called startTransaction().
+ *
+ * The implementation is based on the command pattern, in case
+ * someone is interested.
+ */
+template <class Key, class T>
+class MyMoneyMap : protected QMap<Key, T>
+{
+public:
+ // typedef QMapConstIterator<Key, T> const_iterator;
+
+ MyMoneyMap() : QMap<Key, T>() {}
+ virtual ~MyMoneyMap() {}
+
+ void startTransaction(unsigned long* id = 0)
+ {
+ m_stack.push(new MyMoneyMapStart(this, id));
+ }
+
+ void rollbackTransaction(void)
+ {
+ if(m_stack.count() == 0)
+ throw new MYMONEYEXCEPTION("No transaction started to rollback changes");
+
+ // undo all actions
+ MyMoneyMapAction* action;
+ while(m_stack.count()) {
+ action = m_stack.pop();
+ action->undo();
+ delete action;
+ }
+ }
+
+ bool commitTransaction(void)
+ {
+ if(m_stack.count() == 0)
+ throw new MYMONEYEXCEPTION("No transaction started to commit changes");
+
+ bool rc = m_stack.count() > 1;
+ m_stack.setAutoDelete(true);
+ m_stack.clear();
+ return rc;
+ }
+
+ void insert(const Key& key, const T& obj)
+ {
+ if(m_stack.count() == 0)
+ throw new MYMONEYEXCEPTION("No transaction started to insert new element into container");
+
+ // store object in
+ m_stack.push(new MyMoneyMapInsert(this, key, obj));
+ }
+
+ void modify(const Key& key, const T& obj)
+ {
+ if(m_stack.count() == 0)
+ throw new MYMONEYEXCEPTION("No transaction started to modify element in container");
+
+#if 0
+ // had to take this out, because we use QPair in one instance as key
+ if(key.isEmpty())
+ throw new MYMONEYEXCEPTION("No key to update object");
+#endif
+
+ m_stack.push(new MyMoneyMapModify(this, key, obj));
+ }
+
+ void remove(const Key& key)
+ {
+ if(m_stack.count() == 0)
+ throw new MYMONEYEXCEPTION("No transaction started to remove element from container");
+
+#if 0
+ // had to take this out, because we use QPair in one instance as key
+ if(key.isEmpty())
+ throw new MYMONEYEXCEPTION("No key to remove object");
+#endif
+
+ m_stack.push(new MyMoneyMapRemove(this, key));
+ }
+
+ MyMoneyMap<Key, T>& operator= (const QMap<Key, T>& m)
+ {
+ if(m_stack.count() != 0) {
+ throw new MYMONEYEXCEPTION("Cannot assign whole container during transaction");
+ }
+ QMap<Key, T>::operator=(m);
+ return *this;
+ }
+
+
+ inline QValueList<T> values(void) const
+ {
+ return QMap<Key,T>::values();
+ }
+
+ inline QValueList<Key> keys(void) const
+ {
+ return QMap<Key,T>::keys();
+ }
+
+ const T& operator[] ( const Key& k ) const
+ { QT_CHECK_INVALID_MAP_ELEMENT; return QMap<Key,T>::operator[](k); }
+
+ inline Q_TYPENAME QMap<Key, T>::const_iterator find(const Key& k) const
+ {
+ return QMap<Key,T>::find(k);
+ }
+
+ inline Q_TYPENAME QMap<Key, T>::const_iterator begin(void) const
+ {
+ return QMap<Key,T>::begin();
+ }
+
+ inline Q_TYPENAME QMap<Key, T>::const_iterator end(void) const
+ {
+ return QMap<Key,T>::end();
+ }
+
+ inline bool contains(const Key& k) const
+ {
+ return find(k) != end();
+ }
+
+ inline void map(QMap<Key, T>& that) const
+ {
+ //QMap<Key, T>* ptr = dynamic_cast<QMap<Key, T>* >(this);
+ //that = *ptr;
+ that = *(dynamic_cast<QMap<Key, T>* >(const_cast<MyMoneyMap<Key, T>* >(this)));
+ }
+
+ inline size_t count(void) const
+ {
+ return QMap<Key, T>::count();
+ }
+
+#if MY_OWN_DEBUG
+ void dump(void) const
+ {
+ printf("Container dump\n");
+ printf(" items in container = %d\n", count());
+ printf(" items on stack = %d\n", m_stack.count());
+
+ const_iterator it;
+ for(it = begin(); it != end(); ++it) {
+ printf(" %s \n", it.key().data());
+ }
+ }
+#endif
+
+private:
+ class MyMoneyMapAction
+ {
+ public:
+ MyMoneyMapAction(QMap<Key, T>* container) :
+ m_container(container) {}
+
+ MyMoneyMapAction(QMap<Key, T>* container, const Key& key, const T& obj) :
+ m_container(container),
+ m_obj(obj),
+ m_key(key) {}
+
+ virtual ~MyMoneyMapAction() {}
+ virtual void undo(void) = 0;
+
+ protected:
+ QMap<Key, T>* m_container;
+ T m_obj;
+ Key m_key;
+ };
+
+ class MyMoneyMapStart : public MyMoneyMapAction
+ {
+ public:
+ MyMoneyMapStart(QMap<Key, T>* container, unsigned long* id) :
+ MyMoneyMapAction(container),
+ m_idPtr(id)
+ {
+ if(id != 0)
+ m_id = *id;
+ }
+ virtual ~MyMoneyMapStart() {}
+ void undo(void)
+ {
+ if(m_idPtr != 0)
+ *m_idPtr = m_id;
+ }
+
+ private:
+ unsigned long* m_idPtr;
+ unsigned long m_id;
+ };
+
+ class MyMoneyMapInsert : public MyMoneyMapAction
+ {
+ public:
+ MyMoneyMapInsert(QMap<Key, T>* container, const Key& key, const T& obj) :
+ MyMoneyMapAction(container, key, obj)
+ {
+ (*container)[key] = obj;
+ }
+
+ virtual ~MyMoneyMapInsert() {}
+ void undo(void)
+ {
+ // m_container->remove(m_key) does not work on GCC 4.0.2
+ // using this-> to access those member does the trick
+ this->m_container->remove(this->m_key);
+ }
+ };
+
+ class MyMoneyMapRemove : public MyMoneyMapAction
+ {
+ public:
+ MyMoneyMapRemove(QMap<Key, T>* container, const Key& key) :
+ MyMoneyMapAction(container, key, (*container)[key])
+ {
+ container->remove(key);
+ }
+
+ virtual ~MyMoneyMapRemove() {}
+ void undo(void)
+ {
+ (*(this->m_container))[this->m_key] = this->m_obj;
+ }
+ };
+
+ class MyMoneyMapModify : public MyMoneyMapAction
+ {
+ public:
+ MyMoneyMapModify(QMap<Key, T>* container, const Key& key, const T& obj) :
+ MyMoneyMapAction(container, key, (*container)[key])
+ {
+ (*container)[key] = obj;
+ }
+
+ virtual ~MyMoneyMapModify() {}
+ void undo(void)
+ {
+ (*(this->m_container))[this->m_key] = this->m_obj;
+ }
+ };
+
+protected:
+ QPtrStack<MyMoneyMapAction> m_stack;
+};
+
+#if MY_OWN_DEBUG
+#include <kmymoney/mymoneyaccount.h>
+#include <kmymoney/mymoneytransaction.h>
+main()
+{
+ MyMoneyMap<QString, MyMoneyAccount> container;
+ MyMoneyMap<QString, MyMoneyTransaction> ct;
+
+ MyMoneyAccount acc;
+ acc.setName("Test");
+ // this should not be possible
+ // container["a"] = acc;
+
+ QValueList<MyMoneyAccount> list;
+ list = container.values();
+
+ MyMoneyAccount b;
+ b.setName("Thomas");
+
+ try {
+ container.startTransaction();
+ container.insert("001", acc);
+ container.dump();
+ container.commitTransaction();
+ acc.setName("123");
+ container.startTransaction();
+ container.modify("001", acc);
+ container.dump();
+ container.rollbackTransaction();
+ container.dump();
+
+ container.startTransaction();
+ container.remove(QString("001"));
+ container.dump();
+ container.rollbackTransaction();
+ container.dump();
+
+ b = container["001"];
+ printf("b.name() = %s\n", b.name().data());
+
+ QMap<QString, MyMoneyAccount>::ConstIterator it;
+ it = container.find("001");
+ it = container.begin();
+
+ } catch(MyMoneyException *e) {
+ printf("Caught exception: %s\n", e->what().data());
+ delete e;
+ }
+
+ QMap<QString, MyMoneyAccount> map;
+ map["005"] = b;
+ container = map;
+
+ printf("b.name() = %s\n", container["001"].name().data());
+ printf("b.name() = %s\n", container["005"].name().data());
+}
+
+#endif
+
+#endif
diff --git a/kmymoney2/mymoney/storage/mymoneymaptest.cpp b/kmymoney2/mymoney/storage/mymoneymaptest.cpp
new file mode 100644
index 0000000..5104fad
--- /dev/null
+++ b/kmymoney2/mymoney/storage/mymoneymaptest.cpp
@@ -0,0 +1,38 @@
+/***************************************************************************
+ mymoneymaptest.cpp
+ -------------------
+ copyright : (C) 2007 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "mymoneymaptest.h"
+#include <iostream>
+
+MyMoneyMapTest::MyMoneyMapTest()
+{
+}
+
+
+void MyMoneyMapTest::setUp()
+{
+ m = new MyMoneyMap<QString, QString>;
+}
+
+void MyMoneyMapTest::tearDown()
+{
+ delete m;
+}
+
+void MyMoneyMapTest::testArrayOperator()
+{
+}
+
diff --git a/kmymoney2/mymoney/storage/mymoneymaptest.h b/kmymoney2/mymoney/storage/mymoneymaptest.h
new file mode 100644
index 0000000..c089a3f
--- /dev/null
+++ b/kmymoney2/mymoney/storage/mymoneymaptest.h
@@ -0,0 +1,47 @@
+/***************************************************************************
+ mymoneymaptest.h
+ -------------------
+ copyright : (C) 2007 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 __MYMONEYMAPTEST_H__
+#define __MYMONEYMAPTEST_H__
+
+#include <cppunit/TestCaller.h>
+#include <cppunit/TestCase.h>
+#include <cppunit/TestSuite.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include "../autotest.h"
+
+#define private public
+#include "mymoneyseqaccessmgr.h"
+#undef private
+
+class MyMoneyMapTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(MyMoneyMapTest);
+ CPPUNIT_TEST(testArrayOperator);
+ CPPUNIT_TEST_SUITE_END();
+
+protected:
+ MyMoneyMap<QString, QString> *m;
+public:
+ MyMoneyMapTest();
+
+
+ void setUp();
+ void tearDown();
+ void testArrayOperator(void);
+};
+
+#endif
diff --git a/kmymoney2/mymoney/storage/mymoneyseqaccessmgr.cpp b/kmymoney2/mymoney/storage/mymoneyseqaccessmgr.cpp
new file mode 100644
index 0000000..7341ec1
--- /dev/null
+++ b/kmymoney2/mymoney/storage/mymoneyseqaccessmgr.cpp
@@ -0,0 +1,1944 @@
+/***************************************************************************
+ mymoneyseqaccessmgr.cpp
+ -------------------
+ begin : Sun May 5 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ 2002 Thomas Baumgart
+ email : mte@users.sourceforge.net
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 <typeinfo>
+#include "mymoneyseqaccessmgr.h"
+#include "../mymoneytransactionfilter.h"
+#include "../mymoneycategory.h"
+
+#define TRY try {
+#define CATCH } catch (MyMoneyException *e) {
+#define PASS } catch (MyMoneyException *e) { throw; }
+
+bool MyMoneyBalanceCacheItem::operator ==(const MyMoneyBalanceCacheItem & right) const
+{
+ return ((balance == right.balance)
+ && (valid == right.valid));
+}
+
+MyMoneySeqAccessMgr::MyMoneySeqAccessMgr()
+{
+ m_nextAccountID = 0;
+ m_nextInstitutionID = 0;
+ m_nextTransactionID = 0;
+ m_nextPayeeID = 0;
+ m_nextScheduleID = 0;
+ m_nextSecurityID = 0;
+ m_nextReportID = 0;
+ m_nextBudgetID = 0;
+ m_user = MyMoneyPayee();
+ m_dirty = false;
+ m_creationDate = QDate::currentDate();
+
+ // setup standard accounts
+ MyMoneyAccount acc_l;
+ acc_l.setAccountType(MyMoneyAccount::Liability);
+ acc_l.setName("Liability");
+ MyMoneyAccount liability(STD_ACC_LIABILITY, acc_l);
+
+ MyMoneyAccount acc_a;
+ acc_a.setAccountType(MyMoneyAccount::Asset);
+ acc_a.setName("Asset");
+ MyMoneyAccount asset(STD_ACC_ASSET, acc_a);
+
+ MyMoneyAccount acc_e;
+ acc_e.setAccountType(MyMoneyAccount::Expense);
+ acc_e.setName("Expense");
+ MyMoneyAccount expense(STD_ACC_EXPENSE, acc_e);
+
+ MyMoneyAccount acc_i;
+ acc_i.setAccountType(MyMoneyAccount::Income);
+ acc_i.setName("Income");
+ MyMoneyAccount income(STD_ACC_INCOME, acc_i);
+
+ MyMoneyAccount acc_q;
+ acc_q.setAccountType(MyMoneyAccount::Equity);
+ acc_q.setName("Equity");
+ MyMoneyAccount equity(STD_ACC_EQUITY, acc_q);
+
+ QMap<QString, MyMoneyAccount> map;
+ map[STD_ACC_ASSET] = asset;
+ map[STD_ACC_LIABILITY] = liability;
+ map[STD_ACC_INCOME] = income;
+ map[STD_ACC_EXPENSE] = expense;
+ map[STD_ACC_EQUITY] = equity;
+
+ // load account list with inital accounts
+ m_accountList = map;
+
+ MyMoneyBalanceCacheItem balance;
+
+ m_balanceCache.clear();
+ m_balanceCache[STD_ACC_LIABILITY] = balance;
+ m_balanceCache[STD_ACC_ASSET] = balance;
+ m_balanceCache[STD_ACC_EXPENSE] = balance;
+ m_balanceCache[STD_ACC_INCOME] = balance;
+ m_balanceCache[STD_ACC_EQUITY] = balance;
+
+ // initialize for file fixes (see kmymoneyview.cpp)
+ m_currentFixVersion = 2;
+ m_fileFixVersion = 0; // default value if no fix-version in file
+ m_transactionListFull = false;
+}
+
+MyMoneySeqAccessMgr::~MyMoneySeqAccessMgr()
+{
+}
+
+MyMoneySeqAccessMgr const * MyMoneySeqAccessMgr::duplicate(void)
+{
+ MyMoneySeqAccessMgr* that = new MyMoneySeqAccessMgr();
+ *that = *this;
+ return that;
+}
+ /**
+ * This method is used to get a SQL reader for subsequent database access
+ */
+KSharedPtr <MyMoneyStorageSql> MyMoneySeqAccessMgr::connectToDatabase
+ (const KURL& /*url*/) {
+ return 0;
+}
+
+bool MyMoneySeqAccessMgr::isStandardAccount(const QString& id) const
+{
+ return id == STD_ACC_LIABILITY
+ || id == STD_ACC_ASSET
+ || id == STD_ACC_EXPENSE
+ || id == STD_ACC_INCOME
+ || id == STD_ACC_EQUITY;
+}
+
+void MyMoneySeqAccessMgr::setAccountName(const QString& id, const QString& name)
+{
+ if(!isStandardAccount(id))
+ throw new MYMONEYEXCEPTION("Only standard accounts can be modified using setAccountName()");
+
+ MyMoneyAccount acc = m_accountList[id];
+ acc.setName(name);
+ m_accountList.modify(acc.id(), acc);
+}
+
+const MyMoneyAccount MyMoneySeqAccessMgr::account(const QString& id) const
+{
+ // locate the account and if present, return it's data
+ if(m_accountList.find(id) != m_accountList.end())
+ return m_accountList[id];
+
+ // throw an exception, if it does not exist
+ QString msg = "Unknown account id '" + id + "'";
+ throw new MYMONEYEXCEPTION(msg);
+}
+
+void MyMoneySeqAccessMgr::accountList(QValueList<MyMoneyAccount>& list) const
+{
+ QMap<QString, MyMoneyAccount>::ConstIterator it;
+ for(it = m_accountList.begin(); it != m_accountList.end(); ++it) {
+ if(!isStandardAccount((*it).id())) {
+ list.append(*it);
+ }
+ }
+}
+
+void MyMoneySeqAccessMgr::addAccount(MyMoneyAccount& account)
+{
+ // create the account.
+ MyMoneyAccount newAccount(nextAccountID(), account);
+ m_accountList.insert(newAccount.id(), newAccount);
+
+ account = newAccount;
+}
+
+void MyMoneySeqAccessMgr::addPayee(MyMoneyPayee& payee)
+{
+ // create the payee
+ MyMoneyPayee newPayee(nextPayeeID(), payee);
+ m_payeeList.insert(newPayee.id(), newPayee);
+ payee = newPayee;
+}
+
+const MyMoneyPayee MyMoneySeqAccessMgr::payee(const QString& id) const
+{
+ QMap<QString, MyMoneyPayee>::ConstIterator it;
+ it = m_payeeList.find(id);
+ if(it == m_payeeList.end())
+ throw new MYMONEYEXCEPTION("Unknown payee '" + id + "'");
+
+ return *it;
+}
+
+const MyMoneyPayee MyMoneySeqAccessMgr::payeeByName(const QString& payee) const
+{
+ if(payee.isEmpty())
+ return MyMoneyPayee::null;
+
+ QMap<QString, MyMoneyPayee>::ConstIterator it_p;
+
+ for(it_p = m_payeeList.begin(); it_p != m_payeeList.end(); ++it_p) {
+ if((*it_p).name() == payee) {
+ return *it_p;
+ }
+ }
+
+ throw new MYMONEYEXCEPTION("Unknown payee '" + payee + "'");
+}
+
+void MyMoneySeqAccessMgr::modifyPayee(const MyMoneyPayee& payee)
+{
+ QMap<QString, MyMoneyPayee>::ConstIterator it;
+
+ it = m_payeeList.find(payee.id());
+ if(it == m_payeeList.end()) {
+ QString msg = "Unknown payee '" + payee.id() + "'";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+ m_payeeList.modify((*it).id(), payee);
+}
+
+void MyMoneySeqAccessMgr::removePayee(const MyMoneyPayee& payee)
+{
+ QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
+ QMap<QString, MyMoneySchedule>::ConstIterator it_s;
+ QMap<QString, MyMoneyPayee>::ConstIterator it_p;
+
+ it_p = m_payeeList.find(payee.id());
+ if(it_p == m_payeeList.end()) {
+ QString msg = "Unknown payee '" + payee.id() + "'";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+
+ // scan all transactions to check if the payee is still referenced
+ for(it_t = m_transactionList.begin(); it_t != m_transactionList.end(); ++it_t) {
+ if((*it_t).hasReferenceTo(payee.id())) {
+ throw new MYMONEYEXCEPTION(QString("Cannot remove payee that is still referenced to a %1").arg("transaction"));
+ }
+ }
+
+ // check referential integrity in schedules
+ for(it_s = m_scheduleList.begin(); it_s != m_scheduleList.end(); ++it_s) {
+ if((*it_s).hasReferenceTo(payee.id())) {
+ throw new MYMONEYEXCEPTION(QString("Cannot remove payee that is still referenced to a %1").arg("schedule"));
+ }
+ }
+
+ // remove any reference to report and/or budget
+ removeReferences(payee.id());
+
+ m_payeeList.remove((*it_p).id());
+}
+
+const QValueList<MyMoneyPayee> MyMoneySeqAccessMgr::payeeList(void) const
+{
+ return m_payeeList.values();
+}
+
+
+void MyMoneySeqAccessMgr::addAccount(MyMoneyAccount& parent, MyMoneyAccount& account)
+{
+ QMap<QString, MyMoneyAccount>::ConstIterator theParent;
+ QMap<QString, MyMoneyAccount>::ConstIterator theChild;
+
+ theParent = m_accountList.find(parent.id());
+ if(theParent == m_accountList.end()) {
+ QString msg = "Unknown parent account '";
+ msg += parent.id() + "'";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+
+ theChild = m_accountList.find(account.id());
+ if(theChild == m_accountList.end()) {
+ QString msg = "Unknown child account '";
+ msg += account.id() + "'";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+
+ MyMoneyAccount acc = *theParent;
+ acc.addAccountId(account.id());
+ m_accountList.modify(acc.id(), acc);
+ parent = acc;
+
+ acc = *theChild;
+ acc.setParentAccountId(parent.id());
+ m_accountList.modify(acc.id(), acc);
+ account = acc;
+
+ MyMoneyBalanceCacheItem balance;
+ m_balanceCache[account.id()] = balance;
+}
+
+void MyMoneySeqAccessMgr::addInstitution(MyMoneyInstitution& institution)
+{
+ MyMoneyInstitution newInstitution(nextInstitutionID(), institution);
+
+ m_institutionList.insert(newInstitution.id(), newInstitution);
+
+ // return new data
+ institution = newInstitution;
+}
+
+unsigned int MyMoneySeqAccessMgr::transactionCount(const QString& account) const
+{
+ unsigned int cnt = 0;
+
+ if(account.length() == 0) {
+ cnt = m_transactionList.count();
+
+ } else {
+ QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+
+ // scan all transactions
+ for(it_t = m_transactionList.begin(); it_t != m_transactionList.end(); ++it_t) {
+
+ // scan all splits of this transaction
+ for(it_s = (*it_t).splits().begin(); it_s != (*it_t).splits().end(); ++it_s) {
+ // is it a split in our account?
+ if((*it_s).accountId() == account) {
+ // since a transaction can only have one split referencing
+ // each account, we're done with the splits here!
+ break;
+ }
+ }
+ // if no split contains the account id, continue with the
+ // next transaction
+ if(it_s == (*it_t).splits().end())
+ continue;
+
+ // otherwise count it
+ ++cnt;
+ }
+ }
+ return cnt;
+}
+
+const QMap<QString, unsigned long> MyMoneySeqAccessMgr::transactionCountMap(void) const
+{
+ QMap<QString, unsigned long> map;
+ QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+
+ // scan all transactions
+ for(it_t = m_transactionList.begin(); it_t != m_transactionList.end(); ++it_t) {
+ // scan all splits of this transaction
+ for(it_s = (*it_t).splits().begin(); it_s != (*it_t).splits().end(); ++it_s) {
+ map[(*it_s).accountId()]++;
+ }
+ }
+ return map;
+}
+
+unsigned int MyMoneySeqAccessMgr::institutionCount(void) const
+{
+ return m_institutionList.count();
+}
+
+unsigned int MyMoneySeqAccessMgr::accountCount(void) const
+{
+ return m_accountList.count();
+}
+
+QString MyMoneySeqAccessMgr::nextPayeeID(void)
+{
+ QString id;
+ id.setNum(++m_nextPayeeID);
+ id = "P" + id.rightJustify(PAYEE_ID_SIZE, '0');
+ return id;
+}
+
+QString MyMoneySeqAccessMgr::nextInstitutionID(void)
+{
+ QString id;
+ id.setNum(++m_nextInstitutionID);
+ id = "I" + id.rightJustify(INSTITUTION_ID_SIZE, '0');
+ return id;
+}
+
+QString MyMoneySeqAccessMgr::nextAccountID(void)
+{
+ QString id;
+ id.setNum(++m_nextAccountID);
+ id = "A" + id.rightJustify(ACCOUNT_ID_SIZE, '0');
+ return id;
+}
+
+QString MyMoneySeqAccessMgr::nextTransactionID(void)
+{
+ QString id;
+ id.setNum(++m_nextTransactionID);
+ id = "T" + id.rightJustify(TRANSACTION_ID_SIZE, '0');
+ return id;
+}
+
+QString MyMoneySeqAccessMgr::nextScheduleID(void)
+{
+ QString id;
+ id.setNum(++m_nextScheduleID);
+ id = "SCH" + id.rightJustify(SCHEDULE_ID_SIZE, '0');
+ return id;
+}
+
+QString MyMoneySeqAccessMgr::nextSecurityID(void)
+{
+ QString id;
+ id.setNum(++m_nextSecurityID);
+ id = "E" + id.rightJustify(SECURITY_ID_SIZE, '0');
+ return id;
+}
+
+
+void MyMoneySeqAccessMgr::addTransaction(MyMoneyTransaction& transaction, const bool skipAccountUpdate)
+{
+ // perform some checks to see that the transaction stuff is OK. For
+ // now we assume that
+ // * no ids are assigned
+ // * the date valid (must not be empty)
+ // * the referenced accounts in the splits exist
+
+ // first perform all the checks
+ if(!transaction.id().isEmpty())
+ throw new MYMONEYEXCEPTION("transaction already contains an id");
+ if(!transaction.postDate().isValid())
+ throw new MYMONEYEXCEPTION("invalid post date");
+
+ // now check the splits
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+ for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
+ // the following lines will throw an exception if the
+ // account or payee do not exist
+ account((*it_s).accountId());
+ if(!(*it_s).payeeId().isEmpty())
+ payee((*it_s).payeeId());
+ }
+
+ MyMoneyTransaction newTransaction(nextTransactionID(), transaction);
+ QString key = newTransaction.uniqueSortKey();
+
+ m_transactionList.insert(key, newTransaction);
+ m_transactionKeys.insert(newTransaction.id(), key);
+
+ transaction = newTransaction;
+
+ // adjust the balance of all affected accounts
+ for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
+ MyMoneyAccount acc = m_accountList[(*it_s).accountId()];
+ acc.adjustBalance(*it_s);
+ if(!skipAccountUpdate) {
+ acc.touch();
+ invalidateBalanceCache(acc.id());
+ }
+ m_accountList.modify(acc.id(), acc);
+ }
+}
+
+void MyMoneySeqAccessMgr::touch(void)
+{
+ m_dirty = true;
+ m_lastModificationDate = QDate::currentDate();
+}
+
+bool MyMoneySeqAccessMgr::hasActiveSplits(const QString& id) const
+{
+ QMap<QString, MyMoneyTransaction>::ConstIterator it;
+
+ for(it = m_transactionList.begin(); it != m_transactionList.end(); ++it) {
+ if((*it).accountReferenced(id)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+const MyMoneyInstitution MyMoneySeqAccessMgr::institution(const QString& id) const
+{
+ QMap<QString, MyMoneyInstitution>::ConstIterator pos;
+
+ pos = m_institutionList.find(id);
+ if(pos != m_institutionList.end())
+ return *pos;
+ throw new MYMONEYEXCEPTION("unknown institution");
+}
+
+const QValueList<MyMoneyInstitution> MyMoneySeqAccessMgr::institutionList(void) const
+{
+ return m_institutionList.values();
+}
+
+void MyMoneySeqAccessMgr::modifyAccount(const MyMoneyAccount& account, const bool skipCheck)
+{
+ QMap<QString, MyMoneyAccount>::ConstIterator pos;
+
+ // locate the account in the file global pool
+ pos = m_accountList.find(account.id());
+ if(pos != m_accountList.end()) {
+ // check if the new info is based on the old one.
+ // this is the case, when the file and the id
+ // as well as the type are equal.
+ if((((*pos).parentAccountId() == account.parentAccountId())
+ && ((*pos).accountType() == account.accountType()))
+ || (skipCheck == true)) {
+ // make sure that all the referenced objects exist
+ if(!account.institutionId().isEmpty())
+ institution(account.institutionId());
+
+ QValueList<QString>::ConstIterator it_a;
+ for(it_a = account.accountList().begin(); it_a != account.accountList().end(); ++it_a) {
+ this->account(*it_a);
+ }
+
+ // update information in account list
+ m_accountList.modify(account.id(), account);
+
+ // invalidate cached balance
+ invalidateBalanceCache(account.id());
+
+ } else
+ throw new MYMONEYEXCEPTION("Invalid information for update");
+
+ } else
+ throw new MYMONEYEXCEPTION("Unknown account id");
+}
+
+void MyMoneySeqAccessMgr::modifyInstitution(const MyMoneyInstitution& institution)
+{
+ QMap<QString, MyMoneyInstitution>::ConstIterator pos;
+
+ // locate the institution in the file global pool
+ pos = m_institutionList.find(institution.id());
+ if(pos != m_institutionList.end()) {
+ m_institutionList.modify(institution.id(), institution);
+
+ } else
+ throw new MYMONEYEXCEPTION("unknown institution");
+}
+
+void MyMoneySeqAccessMgr::modifyTransaction(const MyMoneyTransaction& transaction)
+{
+ // perform some checks to see that the transaction stuff is OK. For
+ // now we assume that
+ // * ids are assigned
+ // * the pointer to the MyMoneyFile object is not 0
+ // * the date valid (must not be empty)
+ // * the splits must have valid account ids
+
+ // first perform all the checks
+ if(transaction.id().isEmpty()
+// || transaction.file() != this
+ || !transaction.postDate().isValid())
+ throw new MYMONEYEXCEPTION("invalid transaction to be modified");
+
+ // now check the splits
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+ for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
+ // the following lines will throw an exception if the
+ // account or payee do not exist
+ account((*it_s).accountId());
+ if(!(*it_s).payeeId().isEmpty())
+ payee((*it_s).payeeId());
+ }
+
+ // new data seems to be ok. find old version of transaction
+ // in our pool. Throw exception if unknown.
+ if(!m_transactionKeys.contains(transaction.id()))
+ throw new MYMONEYEXCEPTION("invalid transaction id");
+
+ QString oldKey = m_transactionKeys[transaction.id()];
+ if(!m_transactionList.contains(oldKey))
+ throw new MYMONEYEXCEPTION("invalid transaction key");
+
+ QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
+
+ it_t = m_transactionList.find(oldKey);
+ if(it_t == m_transactionList.end())
+ throw new MYMONEYEXCEPTION("invalid transaction key");
+
+ // adjust account balances
+ for(it_s = (*it_t).splits().begin(); it_s != (*it_t).splits().end(); ++it_s) {
+ MyMoneyAccount acc = m_accountList[(*it_s).accountId()];
+ acc.adjustBalance(*it_s, true); // reverse the adjust operation (reverse = true)
+ acc.touch();
+ invalidateBalanceCache(acc.id());
+ m_accountList.modify(acc.id(), acc);
+ }
+ for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
+ MyMoneyAccount acc = m_accountList[(*it_s).accountId()];
+ acc.adjustBalance(*it_s);
+ acc.touch();
+ invalidateBalanceCache(acc.id());
+ m_accountList.modify(acc.id(), acc);
+ }
+
+ // remove old transaction from lists
+ m_transactionList.remove(oldKey);
+
+ // add new transaction to lists
+ QString newKey = transaction.uniqueSortKey();
+ m_transactionList.insert(newKey, transaction);
+ m_transactionKeys.modify(transaction.id(), newKey);
+}
+
+void MyMoneySeqAccessMgr::reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent)
+{
+ reparentAccount(account, parent, true);
+}
+
+void MyMoneySeqAccessMgr::reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent, const bool /* sendNotification */)
+{
+ QMap<QString, MyMoneyAccount>::ConstIterator oldParent;
+ QMap<QString, MyMoneyAccount>::ConstIterator newParent;
+ QMap<QString, MyMoneyAccount>::ConstIterator childAccount;
+
+ // verify that accounts exist. If one does not,
+ // an exception is thrown
+ MyMoneySeqAccessMgr::account(account.id());
+ MyMoneySeqAccessMgr::account(parent.id());
+ if(!account.parentAccountId().isEmpty()) {
+ MyMoneySeqAccessMgr::account(account.parentAccountId());
+ oldParent = m_accountList.find(account.parentAccountId());
+ }
+
+ if(account.accountType() == MyMoneyAccount::Stock && parent.accountType() != MyMoneyAccount::Investment)
+ throw new MYMONEYEXCEPTION("Cannot move a stock acocunt into a non-investment account");
+
+ newParent = m_accountList.find(parent.id());
+ childAccount = m_accountList.find(account.id());
+
+ MyMoneyAccount acc;
+ if(!account.parentAccountId().isEmpty()) {
+ acc = (*oldParent);
+ acc.removeAccountId(account.id());
+ m_accountList.modify(acc.id(), acc);
+ }
+
+ parent = (*newParent);
+ parent.addAccountId(account.id());
+ m_accountList.modify(parent.id(), parent);
+
+ account = (*childAccount);
+ account.setParentAccountId(parent.id());
+ m_accountList.modify(account.id(), account);
+
+#if 0
+ // make sure the type is the same as the new parent. This does not work for stock and investment
+ if(account.accountType() != MyMoneyAccount::Stock && account.accountType() != MyMoneyAccount::Investment)
+ (*childAccount).setAccountType((*newParent).accountType());
+#endif
+}
+
+void MyMoneySeqAccessMgr::removeTransaction(const MyMoneyTransaction& transaction)
+{
+ // first perform all the checks
+ if(transaction.id().isEmpty())
+ throw new MYMONEYEXCEPTION("invalid transaction to be deleted");
+
+ QMap<QString, QString>::ConstIterator it_k;
+ QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
+
+ it_k = m_transactionKeys.find(transaction.id());
+ if(it_k == m_transactionKeys.end())
+ throw new MYMONEYEXCEPTION("invalid transaction to be deleted");
+
+ it_t = m_transactionList.find(*it_k);
+ if(it_t == m_transactionList.end())
+ throw new MYMONEYEXCEPTION("invalid transaction key");
+
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+
+ // scan the splits and collect all accounts that need
+ // to be updated after the removal of this transaction
+ for(it_s = (*it_t).splits().begin(); it_s != (*it_t).splits().end(); ++it_s) {
+ MyMoneyAccount acc = m_accountList[(*it_s).accountId()];
+ acc.adjustBalance(*it_s, true); // reverse = true
+ acc.touch();
+ m_accountList.modify(acc.id(), acc);
+ invalidateBalanceCache(acc.id());
+ }
+
+ // FIXME: check if any split is frozen and throw exception
+
+ // remove the transaction from the two lists
+ m_transactionList.remove(*it_k);
+ m_transactionKeys.remove(transaction.id());
+}
+
+void MyMoneySeqAccessMgr::removeAccount(const MyMoneyAccount& account)
+{
+ MyMoneyAccount parent;
+
+ // check that the account and it's parent exist
+ // this will throw an exception if the id is unknown
+ MyMoneySeqAccessMgr::account(account.id());
+ parent = MyMoneySeqAccessMgr::account(account.parentAccountId());
+
+ // check that it's not one of the standard account groups
+ if(isStandardAccount(account.id()))
+ throw new MYMONEYEXCEPTION("Unable to remove the standard account groups");
+
+ if(hasActiveSplits(account.id())) {
+ throw new MYMONEYEXCEPTION("Unable to remove account with active splits");
+ }
+
+ // re-parent all sub-ordinate accounts to the parent of the account
+ // to be deleted. First round check that all accounts exist, second
+ // round do the re-parenting.
+ QStringList::ConstIterator it;
+ for(it = account.accountList().begin(); it != account.accountList().end(); ++it) {
+ MyMoneySeqAccessMgr::account(*it);
+ }
+
+ // if one of the accounts did not exist, an exception had been
+ // thrown and we would not make it until here.
+
+ QMap<QString, MyMoneyAccount>::ConstIterator it_a;
+ QMap<QString, MyMoneyAccount>::ConstIterator it_p;
+
+ // locate the account in the file global pool
+
+ it_a = m_accountList.find(account.id());
+ if(it_a == m_accountList.end())
+ throw new MYMONEYEXCEPTION("Internal error: account not found in list");
+
+ it_p = m_accountList.find(parent.id());
+ if(it_p == m_accountList.end())
+ throw new MYMONEYEXCEPTION("Internal error: parent account not found in list");
+
+ if(!account.institutionId().isEmpty())
+ throw new MYMONEYEXCEPTION("Cannot remove account still attached to an institution");
+
+ removeReferences(account.id());
+
+ // FIXME: check referential integrity for the account to be removed
+
+ // check if the new info is based on the old one.
+ // this is the case, when the file and the id
+ // as well as the type are equal.
+ if((*it_a).id() == account.id()
+ && (*it_a).accountType() == account.accountType()) {
+
+ // second round over sub-ordinate accounts: do re-parenting
+ // but only if the list contains at least one entry
+ // FIXME: move this logic to MyMoneyFile
+ if((*it_a).accountList().count() > 0) {
+ while((*it_a).accountList().count() > 0) {
+ it = (*it_a).accountList().begin();
+ MyMoneyAccount acc(MyMoneySeqAccessMgr::account(*it));
+ reparentAccount(acc, parent, false);
+ }
+ }
+ // remove account from parent's list
+ parent.removeAccountId(account.id());
+ m_accountList.modify(parent.id(), parent);
+
+ // remove account from the global account pool
+ m_accountList.remove(account.id());
+
+ // remove from balance list
+ m_balanceCache.remove(account.id());
+ invalidateBalanceCache(parent.id());
+ }
+}
+
+void MyMoneySeqAccessMgr::removeInstitution(const MyMoneyInstitution& institution)
+{
+ QMap<QString, MyMoneyInstitution>::ConstIterator it_i;
+
+ it_i = m_institutionList.find(institution.id());
+ if(it_i != m_institutionList.end()) {
+ m_institutionList.remove(institution.id());
+
+ } else
+ throw new MYMONEYEXCEPTION("invalid institution");
+}
+
+void MyMoneySeqAccessMgr::transactionList(QValueList<MyMoneyTransaction>& list, MyMoneyTransactionFilter& filter) const
+{
+ list.clear();
+
+ QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
+
+ for(it_t = m_transactionList.begin(); it_t != m_transactionList.end(); ++it_t) {
+ // This code is used now. It adds the transaction to the list for
+ // each matching split exactly once. This allows to show information
+ // about different splits in the same register view (e.g. search result)
+ //
+ // I have no idea, if this has some impact on the functionality. So far,
+ // I could not see it. (ipwizard 9/5/2003)
+ if(filter.match(*it_t)) {
+ unsigned int cnt = filter.matchingSplits().count();
+ if(cnt > 1) {
+ for(unsigned i=0; i < cnt; ++i)
+ list.append(*it_t);
+ } else {
+ list.append(*it_t);
+ }
+ }
+ }
+}
+
+void MyMoneySeqAccessMgr::transactionList(QValueList< QPair<MyMoneyTransaction, MyMoneySplit> >& list, MyMoneyTransactionFilter& filter) const
+{
+ list.clear();
+
+ QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
+
+ for(it_t = m_transactionList.begin(); it_t != m_transactionList.end(); ++it_t) {
+ if(filter.match(*it_t)) {
+ QValueList<MyMoneySplit>::const_iterator it_s;
+ for(it_s = filter.matchingSplits().begin(); it_s != filter.matchingSplits().end(); ++it_s) {
+ list.append(qMakePair(*it_t, *it_s));
+ }
+ }
+ }
+}
+
+const QValueList<MyMoneyTransaction> MyMoneySeqAccessMgr::transactionList(MyMoneyTransactionFilter& filter) const
+{
+ QValueList<MyMoneyTransaction> list;
+ transactionList(list, filter);
+ return list;
+}
+
+const MyMoneyTransaction MyMoneySeqAccessMgr::transaction(const QString& id) const
+{
+ // get the full key of this transaction, throw exception
+ // if it's invalid (unknown)
+ if(!m_transactionKeys.contains(id)) {
+ QString msg = QString("Invalid transaction id '%1'").arg(id);
+ throw new MYMONEYEXCEPTION(msg);
+ }
+
+ // check if this key is in the list, throw exception if not
+ QString key = m_transactionKeys[id];
+ if(!m_transactionList.contains(key)) {
+ QString msg = QString("Invalid transaction key '%1'").arg(key);
+ throw new MYMONEYEXCEPTION(msg);
+ }
+
+ return m_transactionList[key];
+}
+
+const MyMoneyTransaction MyMoneySeqAccessMgr::transaction(const QString& account, const int idx) const
+{
+/* removed with MyMoneyAccount::Transaction
+ QMap<QString, MyMoneyAccount>::ConstIterator acc;
+
+ // find account object in list, throw exception if unknown
+ acc = m_accountList.find(account);
+ if(acc == m_accountList.end())
+ throw new MYMONEYEXCEPTION("unknown account id");
+
+ // get the transaction info from the account
+ MyMoneyAccount::Transaction t = (*acc).transaction(idx);
+
+ // return the transaction, throw exception if not found
+ return transaction(t.transactionID());
+*/
+
+ // new implementation if the above code does not work anymore
+ QValueList<MyMoneyTransaction> list;
+ MyMoneyAccount acc = m_accountList[account];
+ MyMoneyTransactionFilter filter;
+
+ if(acc.accountGroup() == MyMoneyAccount::Income
+ || acc.accountGroup() == MyMoneyAccount::Expense)
+ filter.addCategory(account);
+ else
+ filter.addAccount(account);
+
+ transactionList(list, filter);
+ if(idx < 0 || idx >= static_cast<int> (list.count()))
+ throw new MYMONEYEXCEPTION("Unknown idx for transaction");
+
+ return transaction(list[idx].id());
+}
+
+const MyMoneyMoney MyMoneySeqAccessMgr::balance(const QString& id, const QDate& date) const
+{
+ MyMoneyMoney result(0);
+ MyMoneyAccount acc;
+ // if (date != QDate()) qDebug ("request balance for %s at %s", id.data(), date.toString(Qt::ISODate).latin1());
+ if(!date.isValid() && account(id).accountType() != MyMoneyAccount::Stock) {
+ if(m_accountList.find(id) != m_accountList.end())
+ return m_accountList[id].balance();
+ return MyMoneyMoney(0);
+ }
+ if(m_balanceCache[id].valid == false || date != m_balanceCacheDate) {
+ QMap<QString, MyMoneyMoney> balances;
+ QMap<QString, MyMoneyMoney>::ConstIterator it_b;
+ if (date != m_balanceCacheDate) {
+ m_balanceCache.clear();
+ m_balanceCacheDate = date;
+ }
+
+ QValueList<MyMoneyTransaction> list;
+ QValueList<MyMoneyTransaction>::ConstIterator it_t;
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+
+ MyMoneyTransactionFilter filter;
+ filter.setDateFilter(QDate(), date);
+ filter.setReportAllSplits(false);
+ transactionList(list, filter);
+
+ for(it_t = list.begin(); it_t != list.end(); ++it_t) {
+ for(it_s = (*it_t).splits().begin(); it_s != (*it_t).splits().end(); ++it_s){
+ const QString& aid = (*it_s).accountId();
+ if((*it_s).action() == MyMoneySplit::ActionSplitShares) {
+ balances[aid] = balances[aid] * (*it_s).shares();
+ } else {
+ balances[aid] += (*it_s).shares();
+ }
+ }
+ }
+
+ // fill the found balances into the cache
+ for(it_b = balances.begin(); it_b != balances.end(); ++it_b) {
+ MyMoneyBalanceCacheItem balance(*it_b);
+ m_balanceCache[it_b.key()] = balance;
+ }
+
+ // fill all accounts w/o transactions to zero
+ QMap<QString, MyMoneyAccount>::ConstIterator it_a;
+ for(it_a = m_accountList.begin(); it_a != m_accountList.end(); ++it_a) {
+ if(m_balanceCache[(*it_a).id()].valid == false) {
+ MyMoneyBalanceCacheItem balance(MyMoneyMoney(0,1));
+ m_balanceCache[(*it_a).id()] = balance;
+ }
+ }
+ }
+
+ if(m_balanceCache[id].valid == true)
+ result = m_balanceCache[id].balance;
+ else
+ qDebug("Cache mishit should never happen at this point");
+
+ return result;
+}
+
+const MyMoneyMoney MyMoneySeqAccessMgr::totalBalance(const QString& id, const QDate& date) const
+{
+ QStringList accounts;
+ QStringList::ConstIterator it_a;
+
+ MyMoneyMoney result(balance(id, date));
+
+ accounts = account(id).accountList();
+
+ for(it_a = accounts.begin(); it_a != accounts.end(); ++it_a) {
+ result += totalBalance(*it_a, date);
+ }
+
+ return result;
+}
+
+/**
+ * this was intended to move all splits from one account
+ * to another. This somehow is strange to undo because many
+ * changes to different objects are made within one single call.
+ * I kept the source here but commented it out. If we ever need
+ * the functionality, we can turn it back on. BTW: the stuff is untested ;-)
+ */
+/*
+const unsigned int MyMoneyFile::moveSplits(const QString& oldAccount, const QString& newAccount)
+{
+ QMap<QString, MyMoneyTransaction>::Iterator it_t;
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+ unsigned int cnt = 0;
+
+ // scan all transactions
+ for(it_t = m_transactionList.begin(); it_t != m_transactionList.end(); ++it_t) {
+ // scan all splits of this transaction
+ for(it_s = (*it_t).splits().begin(); it_s != (*it_t).splits().end(); ++it_s) {
+ // is it a split in our account?
+ if((*it_s).account() == oldAccount) {
+ MyMoneySplit s = *it_s;
+ s.setAccount(newAccount);
+ (*it_t).modifySplit(s);
+ ++cnt;
+ }
+ }
+ }
+
+ if(cnt != 0) {
+ // now update all the accounts that were referenced
+ QMap<QString, MyMoneyAccount>::Iterator acc;
+ acc = m_accountList.find(oldAccount);
+ if(acc != m_accountList.end()) {
+ (*acc).touch();
+ refreshAccountTransactionList(acc);
+ }
+ acc = m_accountList.find(newAccount);
+ if(acc != m_accountList.end()) {
+ (*acc).touch();
+ refreshAccountTransactionList(acc);
+ }
+
+ // mark file as changed
+ m_dirty = true;
+ }
+ return cnt;
+}
+*/
+
+void MyMoneySeqAccessMgr::invalidateBalanceCache(const QString& id)
+{
+ if(!id.isEmpty()) {
+ try {
+ m_balanceCache[id].valid = false;
+ if(!isStandardAccount(id)) {
+ invalidateBalanceCache(account(id).parentAccountId());
+ }
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+ }
+}
+
+void MyMoneySeqAccessMgr::loadAccounts(const QMap<QString, MyMoneyAccount>& map)
+{
+ m_accountList = map;
+
+ // scan the map to identify the last used id
+ QMap<QString, MyMoneyAccount>::const_iterator it_a;
+ QString lastId("");
+ for(it_a = map.begin(); it_a != map.end(); ++it_a) {
+ if(!isStandardAccount((*it_a).id()) && ((*it_a).id() > lastId))
+ lastId = (*it_a).id();
+ }
+
+ int pos = lastId.find(QRegExp("\\d+"), 0);
+ if(pos != -1) {
+ m_nextAccountID = atol(lastId.mid(pos));
+ }
+}
+
+void MyMoneySeqAccessMgr::loadTransactions(const QMap<QString, MyMoneyTransaction>& map)
+{
+ m_transactionList = map;
+
+ // now fill the key map and
+ // identify the last used id
+ QString lastId("");
+ QMap<QString, QString> keys;
+ QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
+ for(it_t = map.begin(); it_t != map.end(); ++it_t) {
+ keys[(*it_t).id()] = it_t.key();
+ if((*it_t).id() > lastId)
+ lastId = (*it_t).id();
+ }
+ m_transactionKeys = keys;
+
+
+ int pos = lastId.find(QRegExp("\\d+"), 0);
+ if(pos != -1) {
+ m_nextTransactionID = atol(lastId.mid(pos));
+ }
+}
+
+void MyMoneySeqAccessMgr::loadInstitutions(const QMap<QString, MyMoneyInstitution>& map)
+{
+ m_institutionList = map;
+
+ // scan the map to identify the last used id
+ QMap<QString, MyMoneyInstitution>::const_iterator it_i;
+ QString lastId("");
+ for(it_i = map.begin(); it_i != map.end(); ++it_i) {
+ if((*it_i).id() > lastId)
+ lastId = (*it_i).id();
+ }
+
+ int pos = lastId.find(QRegExp("\\d+"), 0);
+ if(pos != -1) {
+ m_nextInstitutionID = atol(lastId.mid(pos));
+ }
+}
+
+void MyMoneySeqAccessMgr::loadPayees(const QMap<QString, MyMoneyPayee>& map)
+{
+ m_payeeList = map;
+
+ // scan the map to identify the last used id
+ QMap<QString, MyMoneyPayee>::const_iterator it_p;
+ QString lastId("");
+ for(it_p = map.begin(); it_p != map.end(); ++it_p) {
+ if((*it_p).id().length() <= PAYEE_ID_SIZE+1) {
+ if((*it_p).id() > lastId)
+ lastId = (*it_p).id();
+ } else {
+ }
+ }
+
+ int pos = lastId.find(QRegExp("\\d+"), 0);
+ if(pos != -1) {
+ m_nextPayeeID = atol(lastId.mid(pos));
+ }
+}
+
+void MyMoneySeqAccessMgr::loadSecurities(const QMap<QString, MyMoneySecurity>& map)
+{
+ m_securitiesList = map;
+
+ // scan the map to identify the last used id
+ QMap<QString, MyMoneySecurity>::const_iterator it_s;
+ QString lastId("");
+ for(it_s = map.begin(); it_s != map.end(); ++it_s) {
+ if((*it_s).id() > lastId)
+ lastId = (*it_s).id();
+ }
+
+ int pos = lastId.find(QRegExp("\\d+"), 0);
+ if(pos != -1) {
+ m_nextSecurityID = atol(lastId.mid(pos));
+ }
+}
+
+void MyMoneySeqAccessMgr::loadCurrencies(const QMap<QString, MyMoneySecurity>& map)
+{
+ m_currencyList = map;
+}
+
+void MyMoneySeqAccessMgr::loadPrices(const MyMoneyPriceList& list)
+{
+ m_priceList = list;
+}
+
+void MyMoneySeqAccessMgr::loadAccountId(const unsigned long id)
+{
+ m_nextAccountID = id;
+}
+
+void MyMoneySeqAccessMgr::loadTransactionId(const unsigned long id)
+{
+ m_nextTransactionID = id;
+}
+
+void MyMoneySeqAccessMgr::loadPayeeId(const unsigned long id)
+{
+ m_nextPayeeID = id;
+}
+
+void MyMoneySeqAccessMgr::loadInstitutionId(const unsigned long id)
+{
+ m_nextInstitutionID = id;
+}
+
+void MyMoneySeqAccessMgr::loadSecurityId(const unsigned long id)
+{
+ m_nextSecurityID = id;
+}
+
+void MyMoneySeqAccessMgr::loadReportId(const unsigned long id)
+{
+ m_nextReportID = id;
+}
+
+void MyMoneySeqAccessMgr::loadBudgetId(const unsigned long id)
+{
+ m_nextBudgetID = id;
+}
+
+const QString MyMoneySeqAccessMgr::value(const QString& key) const
+{
+ return MyMoneyKeyValueContainer::value(key);
+}
+
+void MyMoneySeqAccessMgr::setValue(const QString& key, const QString& val)
+{
+ MyMoneyKeyValueContainer::setValue(key, val);
+ touch();
+}
+
+void MyMoneySeqAccessMgr::deletePair(const QString& key)
+{
+ MyMoneyKeyValueContainer::deletePair(key);
+ touch();
+}
+
+const QMap<QString, QString> MyMoneySeqAccessMgr::pairs(void) const
+{
+ return MyMoneyKeyValueContainer::pairs();
+}
+
+void MyMoneySeqAccessMgr::setPairs(const QMap<QString, QString>& list)
+{
+ MyMoneyKeyValueContainer::setPairs(list);
+ touch();
+}
+
+void MyMoneySeqAccessMgr::addSchedule(MyMoneySchedule& sched)
+{
+ // first perform all the checks
+ if(!sched.id().isEmpty())
+ throw new MYMONEYEXCEPTION("schedule already contains an id");
+
+ // The following will throw an exception when it fails
+ sched.validate(false);
+
+ MyMoneySchedule newSched(nextScheduleID(), sched);
+ m_scheduleList.insert(newSched.id(), newSched);
+ sched = newSched;
+}
+
+void MyMoneySeqAccessMgr::modifySchedule(const MyMoneySchedule& sched)
+{
+ QMap<QString, MyMoneySchedule>::ConstIterator it;
+
+ it = m_scheduleList.find(sched.id());
+ if(it == m_scheduleList.end()) {
+ QString msg = "Unknown schedule '" + sched.id() + "'";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+
+ m_scheduleList.modify(sched.id(), sched);
+}
+
+void MyMoneySeqAccessMgr::removeSchedule(const MyMoneySchedule& sched)
+{
+ QMap<QString, MyMoneySchedule>::ConstIterator it;
+
+ it = m_scheduleList.find(sched.id());
+ if(it == m_scheduleList.end()) {
+ QString msg = "Unknown schedule '" + sched.id() + "'";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+
+ // FIXME: check referential integrity for loan accounts
+ m_scheduleList.remove(sched.id());
+}
+
+const MyMoneySchedule MyMoneySeqAccessMgr::schedule(const QString& id) const
+{
+ QMap<QString, MyMoneySchedule>::ConstIterator pos;
+
+ // locate the schedule and if present, return it's data
+ pos = m_scheduleList.find(id);
+ if(pos != m_scheduleList.end())
+ return (*pos);
+
+ // throw an exception, if it does not exist
+ QString msg = "Unknown schedule id '" + id + "'";
+ throw new MYMONEYEXCEPTION(msg);
+}
+
+const QValueList<MyMoneySchedule> MyMoneySeqAccessMgr::scheduleList(
+ const QString& accountId,
+ const MyMoneySchedule::typeE type,
+ const MyMoneySchedule::occurenceE occurence,
+ const MyMoneySchedule::paymentTypeE paymentType,
+ const QDate& startDate,
+ const QDate& endDate,
+ const bool overdue) const
+{
+ QMap<QString, MyMoneySchedule>::ConstIterator pos;
+ QValueList<MyMoneySchedule> list;
+
+ // qDebug("scheduleList()");
+
+ for(pos = m_scheduleList.begin(); pos != m_scheduleList.end(); ++pos) {
+ // qDebug(" '%s'", qPrintable((*pos).id()));
+
+ if(type != MyMoneySchedule::TYPE_ANY) {
+ if(type != (*pos).type()) {
+ continue;
+ }
+ }
+
+ if(occurence != MyMoneySchedule::OCCUR_ANY) {
+ if(occurence != (*pos).occurence()) {
+ continue;
+ }
+ }
+
+ if(paymentType != MyMoneySchedule::STYPE_ANY) {
+ if(paymentType != (*pos).paymentType()) {
+ continue;
+ }
+ }
+
+ if(!accountId.isEmpty()) {
+ MyMoneyTransaction t = (*pos).transaction();
+ QValueList<MyMoneySplit>::ConstIterator it;
+ QValueList<MyMoneySplit> splits;
+ splits = t.splits();
+ for(it = splits.begin(); it != splits.end(); ++it) {
+ if((*it).accountId() == accountId)
+ break;
+ }
+ if(it == splits.end()) {
+ continue;
+ }
+ }
+
+ if(startDate.isValid() && endDate.isValid()) {
+ if((*pos).paymentDates(startDate, endDate).count() == 0) {
+ continue;
+ }
+ }
+
+ if(startDate.isValid() && !endDate.isValid()) {
+ if(!(*pos).nextPayment(startDate.addDays(-1)).isValid()) {
+ continue;
+ }
+ }
+
+ if(!startDate.isValid() && endDate.isValid()) {
+ if((*pos).startDate() > endDate) {
+ continue;
+ }
+ }
+
+ if(overdue) {
+ if (!(*pos).isOverdue())
+ continue;
+ }
+
+ // qDebug("Adding '%s'", (*pos).name().latin1());
+ list << *pos;
+ }
+ return list;
+}
+
+void MyMoneySeqAccessMgr::loadSchedules(const QMap<QString, MyMoneySchedule>& map)
+{
+ m_scheduleList = map;
+
+ // scan the map to identify the last used id
+ QMap<QString, MyMoneySchedule>::const_iterator it_s;
+ QString lastId("");
+ for(it_s = map.begin(); it_s != map.end(); ++it_s) {
+ if((*it_s).id() > lastId)
+ lastId = (*it_s).id();
+ }
+
+ int pos = lastId.find(QRegExp("\\d+"), 0);
+ if(pos != -1) {
+ m_nextScheduleID = atol(lastId.mid(pos));
+ }
+}
+
+void MyMoneySeqAccessMgr::loadScheduleId(const unsigned long id)
+{
+ m_nextScheduleID = id;
+}
+
+const QValueList<MyMoneySchedule> MyMoneySeqAccessMgr::scheduleListEx(int scheduleTypes,
+ int scheduleOcurrences,
+ int schedulePaymentTypes,
+ QDate date,
+ const QStringList& accounts) const
+{
+// qDebug("scheduleListEx");
+
+ QMap<QString, MyMoneySchedule>::ConstIterator pos;
+ QValueList<MyMoneySchedule> list;
+
+ if (!date.isValid())
+ return list;
+
+ for(pos = m_scheduleList.begin(); pos != m_scheduleList.end(); ++pos)
+ {
+ if (scheduleTypes && !(scheduleTypes & (*pos).type()))
+ continue;
+
+ if (scheduleOcurrences && !(scheduleOcurrences & (*pos).occurence()))
+ continue;
+
+ if (schedulePaymentTypes && !(schedulePaymentTypes & (*pos).paymentType()))
+ continue;
+
+ if((*pos).paymentDates(date, date).count() == 0)
+ continue;
+
+ if ((*pos).isFinished())
+ continue;
+
+ if ((*pos).hasRecordedPayment(date))
+ continue;
+
+ if (accounts.count() > 0)
+ {
+ if (accounts.contains((*pos).account().id()))
+ continue;
+ }
+
+// qDebug("\tAdding '%s'", (*pos).name().latin1());
+ list << *pos;
+ }
+
+ return list;
+}
+
+void MyMoneySeqAccessMgr::addSecurity(MyMoneySecurity& security)
+{
+ // create the account
+ MyMoneySecurity newSecurity(nextSecurityID(), security);
+
+ m_securitiesList.insert(newSecurity.id(), newSecurity);
+
+ security = newSecurity;
+}
+
+void MyMoneySeqAccessMgr::modifySecurity(const MyMoneySecurity& security)
+{
+ QMap<QString, MyMoneySecurity>::ConstIterator it;
+
+ it = m_securitiesList.find(security.id());
+ if(it == m_securitiesList.end())
+ {
+ QString msg = "Unknown security '";
+ msg += security.id() + "' during modifySecurity()";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+
+ m_securitiesList.modify(security.id(), security);
+}
+
+void MyMoneySeqAccessMgr::removeSecurity(const MyMoneySecurity& security)
+{
+ QMap<QString, MyMoneySecurity>::ConstIterator it;
+
+ // FIXME: check referential integrity
+
+ it = m_securitiesList.find(security.id());
+ if(it == m_securitiesList.end())
+ {
+ QString msg = "Unknown security '";
+ msg += security.id() + "' during removeSecurity()";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+
+ m_securitiesList.remove(security.id());
+}
+
+const MyMoneySecurity MyMoneySeqAccessMgr::security(const QString& id) const
+{
+ QMap<QString, MyMoneySecurity>::ConstIterator it = m_securitiesList.find(id);
+ if(it != m_securitiesList.end())
+ {
+ return it.data();
+ }
+
+ return MyMoneySecurity();
+}
+
+const QValueList<MyMoneySecurity> MyMoneySeqAccessMgr::securityList(void) const
+{
+ //qDebug("securityList: Security list size is %d, this=%8p", m_equitiesList.size(), (void*)this);
+ return m_securitiesList.values();
+}
+
+void MyMoneySeqAccessMgr::addCurrency(const MyMoneySecurity& currency)
+{
+ QMap<QString, MyMoneySecurity>::ConstIterator it;
+
+ it = m_currencyList.find(currency.id());
+ if(it != m_currencyList.end()) {
+ throw new MYMONEYEXCEPTION(QString("Cannot add currency with existing id %1").arg(currency.id().data()));
+ }
+
+ m_currencyList.insert(currency.id(), currency);
+}
+
+void MyMoneySeqAccessMgr::modifyCurrency(const MyMoneySecurity& currency)
+{
+ QMap<QString, MyMoneySecurity>::ConstIterator it;
+
+ it = m_currencyList.find(currency.id());
+ if(it == m_currencyList.end()) {
+ throw new MYMONEYEXCEPTION(QString("Cannot modify currency with unknown id %1").arg(currency.id().data()));
+ }
+
+ m_currencyList.modify(currency.id(), currency);
+}
+
+void MyMoneySeqAccessMgr::removeCurrency(const MyMoneySecurity& currency)
+{
+ QMap<QString, MyMoneySecurity>::ConstIterator it;
+
+ // FIXME: check referential integrity
+
+ it = m_currencyList.find(currency.id());
+ if(it == m_currencyList.end()) {
+ throw new MYMONEYEXCEPTION(QString("Cannot remove currency with unknown id %1").arg(currency.id().data()));
+ }
+
+ m_currencyList.remove(currency.id());
+}
+
+const MyMoneySecurity MyMoneySeqAccessMgr::currency(const QString& id) const
+{
+ if(id.isEmpty()) {
+
+ }
+ QMap<QString, MyMoneySecurity>::ConstIterator it;
+
+ it = m_currencyList.find(id);
+ if(it == m_currencyList.end()) {
+ throw new MYMONEYEXCEPTION(QString("Cannot retrieve currency with unknown id '%1'").arg(id.data()));
+ }
+
+ return *it;
+}
+
+const QValueList<MyMoneySecurity> MyMoneySeqAccessMgr::currencyList(void) const
+{
+ return m_currencyList.values();
+}
+
+const QValueList<MyMoneyReport> MyMoneySeqAccessMgr::reportList(void) const
+{
+ return m_reportList.values();
+}
+
+void MyMoneySeqAccessMgr::addReport( MyMoneyReport& report )
+{
+ if(!report.id().isEmpty())
+ throw new MYMONEYEXCEPTION("report already contains an id");
+
+ MyMoneyReport newReport(nextReportID(), report);
+ m_reportList.insert(newReport.id(), newReport);
+ report = newReport;
+}
+
+void MyMoneySeqAccessMgr::loadReports(const QMap<QString, MyMoneyReport>& map)
+{
+ m_reportList = map;
+
+ // scan the map to identify the last used id
+ QMap<QString, MyMoneyReport>::const_iterator it_r;
+ QString lastId("");
+ for(it_r = map.begin(); it_r != map.end(); ++it_r) {
+ if((*it_r).id() > lastId)
+ lastId = (*it_r).id();
+ }
+
+ int pos = lastId.find(QRegExp("\\d+"), 0);
+ if(pos != -1) {
+ m_nextReportID = atol(lastId.mid(pos));
+ }
+}
+
+void MyMoneySeqAccessMgr::modifyReport( const MyMoneyReport& report )
+{
+ QMap<QString, MyMoneyReport>::ConstIterator it;
+
+ it = m_reportList.find(report.id());
+ if(it == m_reportList.end()) {
+ QString msg = "Unknown report '" + report.id() + "'";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+ m_reportList.modify(report.id(), report);
+}
+
+QString MyMoneySeqAccessMgr::nextReportID(void)
+{
+ QString id;
+ id.setNum(++m_nextReportID);
+ id = "R" + id.rightJustify(REPORT_ID_SIZE, '0');
+ return id;
+}
+
+unsigned MyMoneySeqAccessMgr::countReports(void) const
+{
+ return m_reportList.count();
+}
+
+const MyMoneyReport MyMoneySeqAccessMgr::report( const QString& _id ) const
+{
+ return m_reportList[_id];
+}
+
+void MyMoneySeqAccessMgr::removeReport( const MyMoneyReport& report )
+{
+ QMap<QString, MyMoneyReport>::ConstIterator it;
+
+ it = m_reportList.find(report.id());
+ if(it == m_reportList.end()) {
+ QString msg = "Unknown report '" + report.id() + "'";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+
+ m_reportList.remove(report.id());
+}
+
+const QValueList<MyMoneyBudget> MyMoneySeqAccessMgr::budgetList(void) const
+{
+ return m_budgetList.values();
+}
+
+
+void MyMoneySeqAccessMgr::addBudget( MyMoneyBudget& budget )
+{
+ MyMoneyBudget newBudget(nextBudgetID(), budget);
+ m_budgetList.insert(newBudget.id(), newBudget);
+ budget = newBudget;
+}
+
+void MyMoneySeqAccessMgr::loadBudgets(const QMap<QString, MyMoneyBudget>& map)
+{
+ m_budgetList = map;
+
+ // scan the map to identify the last used id
+ QMap<QString, MyMoneyBudget>::const_iterator it_b;
+ QString lastId("");
+ for(it_b = map.begin(); it_b != map.end(); ++it_b) {
+ if((*it_b).id() > lastId)
+ lastId = (*it_b).id();
+ }
+
+ int pos = lastId.find(QRegExp("\\d+"), 0);
+ if(pos != -1) {
+ m_nextBudgetID = atol(lastId.mid(pos));
+ }
+}
+
+const MyMoneyBudget MyMoneySeqAccessMgr::budgetByName(const QString& budget) const
+{
+ QMap<QString, MyMoneyBudget>::ConstIterator it_p;
+
+ for(it_p = m_budgetList.begin(); it_p != m_budgetList.end(); ++it_p) {
+ if((*it_p).name() == budget) {
+ return *it_p;
+ }
+ }
+
+ throw new MYMONEYEXCEPTION("Unknown budget '" + budget + "'");
+}
+
+void MyMoneySeqAccessMgr::modifyBudget( const MyMoneyBudget& budget )
+{
+ QMap<QString, MyMoneyBudget>::ConstIterator it;
+
+ it = m_budgetList.find(budget.id());
+ if(it == m_budgetList.end()) {
+ QString msg = "Unknown budget '" + budget.id() + "'";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+ m_budgetList.modify(budget.id(), budget);
+}
+
+QString MyMoneySeqAccessMgr::nextBudgetID(void)
+{
+ QString id;
+ id.setNum(++m_nextBudgetID);
+ id = "B" + id.rightJustify(BUDGET_ID_SIZE, '0');
+ return id;
+}
+
+unsigned MyMoneySeqAccessMgr::countBudgets(void) const
+{
+ return m_budgetList.count();
+}
+
+MyMoneyBudget MyMoneySeqAccessMgr::budget( const QString& _id ) const
+{
+ return m_budgetList[_id];
+}
+
+void MyMoneySeqAccessMgr::removeBudget( const MyMoneyBudget& budget )
+{
+ QMap<QString, MyMoneyBudget>::ConstIterator it;
+
+ it = m_budgetList.find(budget.id());
+ if(it == m_budgetList.end()) {
+ QString msg = "Unknown budget '" + budget.id() + "'";
+ throw new MYMONEYEXCEPTION(msg);
+ }
+
+ m_budgetList.remove(budget.id());
+}
+
+void MyMoneySeqAccessMgr::addPrice(const MyMoneyPrice& price)
+{
+ MyMoneySecurityPair pricePair(price.from(), price.to());
+ QMap<MyMoneySecurityPair, MyMoneyPriceEntries>::ConstIterator it_m;
+ it_m = m_priceList.find(pricePair);
+
+ MyMoneyPriceEntries entries;
+ if(it_m != m_priceList.end()) {
+ entries = (*it_m);
+ }
+ // entries contains the current entries for this security pair
+ // in case it_m points to m_priceList.end() we need to create a
+ // new entry in the priceList, otherwise we need to modify
+ // an existing one.
+
+ MyMoneyPriceEntries::ConstIterator it;
+ it = entries.find(price.date());
+ if(it != entries.end()) {
+ if((*it).rate(QString()) == price.rate(QString())
+ && (*it).source() == price.source())
+ // in case the information did not change, we don't do anything
+ return;
+ }
+
+ // store new value in local copy
+ entries[price.date()] = price;
+
+ if(it_m != m_priceList.end()) {
+ m_priceList.modify(pricePair, entries);
+ } else {
+ m_priceList.insert(pricePair, entries);
+ }
+}
+
+void MyMoneySeqAccessMgr::removePrice(const MyMoneyPrice& price)
+{
+ MyMoneySecurityPair pricePair(price.from(), price.to());
+ QMap<MyMoneySecurityPair, MyMoneyPriceEntries>::ConstIterator it_m;
+ it_m = m_priceList.find(pricePair);
+
+ MyMoneyPriceEntries entries;
+ if(it_m != m_priceList.end()) {
+ entries = (*it_m);
+ }
+
+ // store new value in local copy
+ entries.remove(price.date());
+
+ if(entries.count() != 0) {
+ m_priceList.modify(pricePair, entries);
+ } else {
+ m_priceList.remove(pricePair);
+ }
+}
+
+const MyMoneyPriceList MyMoneySeqAccessMgr::priceList(void) const
+{
+ MyMoneyPriceList list;
+ m_priceList.map(list);
+ return list;
+}
+
+const MyMoneyPrice MyMoneySeqAccessMgr::price(const QString& fromId, const QString& toId, const QDate& _date, const bool exactDate) const
+{
+ MyMoneyPrice rc;
+ MyMoneyPriceEntries::ConstIterator it;
+ QDate date(_date);
+
+ // If no valid date is passed, we use today's date.
+ if(!date.isValid())
+ date = QDate::currentDate();
+
+ // If the caller selected an exact entry, we can search for
+ // it using the date as the key
+ if(exactDate) {
+ it = m_priceList[MyMoneySecurityPair(fromId, toId)].find(date);
+ if(it != m_priceList[MyMoneySecurityPair(fromId, toId)].end())
+ rc = *it;
+
+ } else {
+ // otherwise, we must scan the map for the previous price entry
+ for(it = m_priceList[MyMoneySecurityPair(fromId, toId)].begin(); it != m_priceList[MyMoneySecurityPair(fromId, toId)].end(); ++it) {
+ if(date < it.key())
+ break;
+
+ if(date >= it.key())
+ rc = *it;
+ }
+ }
+ return rc;
+}
+
+void MyMoneySeqAccessMgr::clearCache(void)
+{
+ m_balanceCache.clear();
+}
+
+void MyMoneySeqAccessMgr::rebuildAccountBalances(void)
+{
+ // reset the balance of all accounts to 0
+ QMap<QString, MyMoneyAccount> map;
+ m_accountList.map(map);
+
+ QMap<QString, MyMoneyAccount>::iterator it_a;
+ for(it_a = map.begin(); it_a != map.end(); ++it_a) {
+ (*it_a).setBalance(MyMoneyMoney(0));
+ }
+
+ // now scan over all transactions and all splits and setup the balances
+ QMap<QString, MyMoneyTransaction>::const_iterator it_t;
+ for(it_t = m_transactionList.begin(); it_t != m_transactionList.end(); ++it_t) {
+ const QValueList<MyMoneySplit>& splits = (*it_t).splits();
+ QValueList<MyMoneySplit>::const_iterator it_s = splits.begin();
+ for(; it_s != splits.end(); ++it_s ) {
+ if(!(*it_s).shares().isZero()) {
+ const QString& id = (*it_s).accountId();
+ // locate the account and if present, update data
+ if(map.find(id) != map.end()) {
+ map[id].adjustBalance(*it_s);
+ }
+ }
+ }
+ }
+
+ m_accountList = map;
+}
+
+bool MyMoneySeqAccessMgr::isReferenced(const MyMoneyObject& obj, const MyMoneyFileBitArray& skipCheck) const
+{
+ // We delete all references in reports when an object
+ // is deleted, so we don't need to check here. See
+ // MyMoneySeqAccessMgr::removeReferences(). In case
+ // you miss the report checks in the following lines ;)
+
+ bool rc = false;
+ const QString& id = obj.id();
+ QMap<QString, MyMoneyTransaction>::const_iterator it_t;
+ QMap<QString, MyMoneyAccount>::const_iterator it_a;
+ QMap<QString, MyMoneyInstitution>::const_iterator it_i;
+ QMap<QString, MyMoneyPayee>::const_iterator it_p;
+ QMap<QString, MyMoneyBudget>::const_iterator it_b;
+ QMap<QString, MyMoneySchedule>::const_iterator it_sch;
+ QMap<QString, MyMoneySecurity>::const_iterator it_sec;
+ MyMoneyPriceList::const_iterator it_pr;
+
+ // FIXME optimize the list of objects we have to checks
+ // with a bit of knowledge of the internal structure, we
+ // could optimize the number of objects we check for references
+
+ // Scan all engine objects for a reference
+ if(!skipCheck[RefCheckTransaction]) {
+ for(it_t = m_transactionList.begin(); !rc && it_t != m_transactionList.end(); ++it_t) {
+ rc = (*it_t).hasReferenceTo(id);
+ }
+ }
+
+ if(!skipCheck[RefCheckAccount]) {
+ for(it_a = m_accountList.begin(); !rc && it_a != m_accountList.end(); ++it_a) {
+ rc = (*it_a).hasReferenceTo(id);
+ }
+ }
+ if(!skipCheck[RefCheckInstitution]) {
+ for(it_i = m_institutionList.begin(); !rc && it_i != m_institutionList.end(); ++it_i) {
+ rc = (*it_i).hasReferenceTo(id);
+ }
+ }
+ if(!skipCheck[RefCheckPayee]) {
+ for(it_p = m_payeeList.begin(); !rc && it_p != m_payeeList.end(); ++it_p) {
+ rc = (*it_p).hasReferenceTo(id);
+ }
+ }
+
+ if(!skipCheck[RefCheckBudget]) {
+ for(it_b = m_budgetList.begin(); !rc && it_b != m_budgetList.end(); ++it_b) {
+ rc = (*it_b).hasReferenceTo(id);
+ }
+ }
+ if(!skipCheck[RefCheckSchedule]) {
+ for(it_sch = m_scheduleList.begin(); !rc && it_sch != m_scheduleList.end(); ++it_sch) {
+ rc = (*it_sch).hasReferenceTo(id);
+ }
+ }
+ if(!skipCheck[RefCheckSecurity]) {
+ for(it_sec = m_securitiesList.begin(); !rc && it_sec != m_securitiesList.end(); ++it_sec) {
+ rc = (*it_sec).hasReferenceTo(id);
+ }
+ }
+ if(!skipCheck[RefCheckCurrency]) {
+ for(it_sec = m_currencyList.begin(); !rc && it_sec != m_currencyList.end(); ++it_sec) {
+ rc = (*it_sec).hasReferenceTo(id);
+ }
+ }
+ // within the pricelist we don't have to scan each entry. Checking the QPair
+ // members of the MyMoneySecurityPair is enough as they are identical to the
+ // two security ids
+ if(!skipCheck[RefCheckPrice]) {
+ for(it_pr = m_priceList.begin(); !rc && it_pr != m_priceList.end(); ++it_pr) {
+ rc = (it_pr.key().first == id) || (it_pr.key().second == id);
+ }
+ }
+
+ return rc;
+}
+
+void MyMoneySeqAccessMgr::startTransaction(void)
+{
+ m_payeeList.startTransaction(&m_nextPayeeID);
+ m_institutionList.startTransaction(&m_nextInstitutionID);
+ m_accountList.startTransaction(&m_nextPayeeID);
+ m_transactionList.startTransaction(&m_nextTransactionID);
+ m_transactionKeys.startTransaction();
+ m_scheduleList.startTransaction(&m_nextScheduleID);
+ m_securitiesList.startTransaction(&m_nextSecurityID);
+ m_currencyList.startTransaction();
+ m_reportList.startTransaction(&m_nextReportID);
+ m_budgetList.startTransaction(&m_nextBudgetID);
+ m_priceList.startTransaction();
+}
+
+bool MyMoneySeqAccessMgr::commitTransaction(void)
+{
+ bool rc = false;
+ rc |= m_payeeList.commitTransaction();
+ rc |= m_institutionList.commitTransaction();
+ rc |= m_accountList.commitTransaction();
+ rc |= m_transactionList.commitTransaction();
+ rc |= m_transactionKeys.commitTransaction();
+ rc |= m_scheduleList.commitTransaction();
+ rc |= m_securitiesList.commitTransaction();
+ rc |= m_currencyList.commitTransaction();
+ rc |= m_reportList.commitTransaction();
+ rc |= m_budgetList.commitTransaction();
+ rc |= m_priceList.commitTransaction();
+
+ // if there was a change, touch the whole storage object
+ if(rc)
+ touch();
+
+ return rc;
+}
+
+void MyMoneySeqAccessMgr::rollbackTransaction(void)
+{
+ m_payeeList.rollbackTransaction();
+ m_institutionList.rollbackTransaction();
+ m_accountList.rollbackTransaction();
+ m_transactionList.rollbackTransaction();
+ m_transactionKeys.rollbackTransaction();
+ m_scheduleList.rollbackTransaction();
+ m_securitiesList.rollbackTransaction();
+ m_currencyList.rollbackTransaction();
+ m_reportList.rollbackTransaction();
+ m_budgetList.rollbackTransaction();
+ m_priceList.rollbackTransaction();
+}
+
+void MyMoneySeqAccessMgr::removeReferences(const QString& id)
+{
+ QMap<QString, MyMoneyReport>::const_iterator it_r;
+ QMap<QString, MyMoneyBudget>::const_iterator it_b;
+
+ // remove from reports
+ for(it_r = m_reportList.begin(); it_r != m_reportList.end(); ++it_r) {
+ MyMoneyReport r = *it_r;
+ r.removeReference(id);
+ m_reportList.modify(r.id(), r);
+ }
+
+ // remove from budgets
+ for(it_b = m_budgetList.begin(); it_b != m_budgetList.end(); ++it_b) {
+ MyMoneyBudget b = *it_b;
+ b.removeReference(id);
+ m_budgetList.modify(b.id(), b);
+ }
+}
+
+#undef TRY
+#undef CATCH
+#undef PASS
+
+// vim:cin:si:ai:et:ts=2:sw=2:
diff --git a/kmymoney2/mymoney/storage/mymoneyseqaccessmgr.h b/kmymoney2/mymoney/storage/mymoneyseqaccessmgr.h
new file mode 100644
index 0000000..f200bef
--- /dev/null
+++ b/kmymoney2/mymoney/storage/mymoneyseqaccessmgr.h
@@ -0,0 +1,1232 @@
+/***************************************************************************
+ mymoneyseqaccessmgr.h - description
+ -------------------
+ begin : Sun May 5 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYSEQACCESSMGR_H
+#define MYMONEYSEQACCESSMGR_H
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "imymoneystorage.h"
+#include "imymoneyserialize.h"
+#include "mymoneymap.h"
+
+/**
+ * @author Thomas Baumgart
+ */
+
+/**
+ * This member represents an item in the balance cache. The balance cache
+ * is used for fast processing of the balance of an account. Several
+ * of these objects are held by the MyMoneySeqAccessMgr() object in a map
+ * with the account Id as key. If such a cache item is present in the map,
+ * the contained balance of it will be used as current balance for this
+ * account. If the balance is changed by any operation, the
+ * MyMoneyBalanceCacheItem for the modified account will be removed from
+ * the map and the next time the balance for this account is requested,
+ * it has to be recalculated. After recalculation, a new MyMoneyBalanceCacheItem
+ * will be created containing the new balance value.
+ *
+ * @see MyMoneySeqAccessMgr::balance() and
+ * MyMoneySeqAccessMgr::invalidateBalanceCache() for a usage example
+ */
+class MyMoneyBalanceCacheItem {
+public:
+ MyMoneyBalanceCacheItem() { valid = false; };
+ MyMoneyBalanceCacheItem(const MyMoneyMoney& val) { balance = val; valid = true; };
+
+ bool operator == (const MyMoneyBalanceCacheItem& right) const;
+ bool valid;
+ MyMoneyMoney balance;
+};
+
+/**
+ * The MyMoneySeqAccessMgr class represents the storage engine for sequential
+ * files. The actual file type and it's internal storage format (e.g. binary
+ * or XML) is not important and handled through the IMyMoneySerialize() interface.
+ *
+ * The MyMoneySeqAccessMgr must be loaded by an application using the
+ * IMyMoneySerialize() interface and can then be accessed through the
+ * IMyMoneyStorage() interface. All data is loaded into memory, modified
+ * and kept there. It is the subject of an outside object to store the
+ * modified data in a persistant storage area using the IMyMoneySerialize()
+ * interface. As indication, if data has been changed, the retrun value
+ * of the method dirty() can be used.
+ */
+class MyMoneySeqAccessMgr : public IMyMoneyStorage, public IMyMoneySerialize,
+ public MyMoneyKeyValueContainer
+{
+public:
+
+ MyMoneySeqAccessMgr();
+ ~MyMoneySeqAccessMgr();
+
+ // general get functions
+ const MyMoneyPayee user(void) const { return m_user; };
+ const QDate creationDate(void) const { return m_creationDate; };
+ const QDate lastModificationDate(void) const { return m_lastModificationDate; };
+ unsigned int currentFixVersion(void) const { return m_currentFixVersion; };
+ unsigned int fileFixVersion(void) const { return m_fileFixVersion; };
+
+
+ // general set functions
+ void setUser(const MyMoneyPayee& user) { m_user = user;
+ touch(); };
+ void setCreationDate(const QDate& val) { m_creationDate = val; touch(); };
+ void setLastModificationDate(const QDate& val) { m_lastModificationDate = val; m_dirty = false; };
+ void setFileFixVersion(const unsigned int v) { m_fileFixVersion = v; };
+ /**
+ * This method is used to get a SQL reader for subsequent database access
+ */
+ KSharedPtr <MyMoneyStorageSql> connectToDatabase (const KURL& url);
+ /**
+ * This method is used when a database file is open, and the data is to
+ * be saved in a different file or format. It will ensure that all data
+ * from the database is available in memory to enable it to be written.
+ */
+ virtual void fillStorage() { };
+
+ /**
+ * This method is used to duplicate the MyMoneySeqAccessMgr object and return
+ * a pointer to the newly created copy. The caller of this method is the
+ * new owner of the object and must destroy it.
+ */
+ MyMoneySeqAccessMgr const * duplicate(void);
+
+ /**
+ * Returns the account addressed by it's id.
+ *
+ * @param id id of the account to locate.
+ * @return reference to MyMoneyAccount object. An exception is thrown
+ * if the id is unknown
+ */
+ const MyMoneyAccount account(const QString& id) const;
+
+ /**
+ * This method is used to check whether a given
+ * account id references one of the standard accounts or not.
+ *
+ * @param id account id
+ * @return true if account-id is one of the standards, false otherwise
+ */
+ bool isStandardAccount(const QString& id) const;
+
+ /**
+ * This method is used to set the name for the specified standard account
+ * within the storage area. An exception will be thrown, if an error
+ * occurs
+ *
+ * @param id QString reference to one of the standard accounts. Possible
+ * values are:
+ *
+ * @li STD_ACC_LIABILITY
+ * @li STD_ACC_ASSET
+ * @li STD_ACC_EXPENSE
+ * @li STD_ACC_INCOME
+ * @li STD_ACC_EQUITY
+ *
+ * @param name QString reference to the name to be set
+ *
+ */
+ void setAccountName(const QString& id, const QString& name);
+
+ /**
+ * This method is used to create a new account
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param account MyMoneyAccount filled with data
+ */
+ void addAccount(MyMoneyAccount& account);
+
+ /**
+ * This method is used to create a new payee
+ *
+ * An exception will be thrown upon error conditions
+ *
+ * @param payee MyMoneyPayee reference to payee information
+ */
+ void addPayee(MyMoneyPayee& payee);
+
+ /**
+ * This method is used to retrieve information about a payee
+ * An exception will be thrown upon error conditions.
+ *
+ * @param id QString reference to id of payee
+ *
+ * @return MyMoneyPayee object of payee
+ */
+ const MyMoneyPayee payee(const QString& id) const;
+
+ /**
+ * This method is used to retrieve the id to a corresponding
+ * name of a payee/receiver.
+ * An exception will be thrown upon error conditions.
+ *
+ * @param payee QString reference to name of payee
+ *
+ * @return MyMoneyPayee reference to object of payee
+ */
+ const MyMoneyPayee payeeByName(const QString& payee) const;
+
+ /**
+ * This method is used to modify an existing payee
+ *
+ * An exception will be thrown upon error conditions
+ *
+ * @param payee MyMoneyPayee reference to payee information
+ */
+ void modifyPayee(const MyMoneyPayee& payee);
+
+ /**
+ * This method is used to remove an existing payee
+ *
+ * An exception will be thrown upon error conditions
+ *
+ * @param payee MyMoneyPayee reference to payee information
+ */
+ void removePayee(const MyMoneyPayee& payee);
+
+ /**
+ * This method returns a list of the payees
+ * inside a MyMoneyStorage object
+ *
+ * @return QValueList<MyMoneyPayee> containing the payee information
+ */
+ const QValueList<MyMoneyPayee> payeeList(void) const;
+
+ /**
+ * This method is used to add one account as sub-ordinate to another
+ * (parent) account. The objects passed as arguments will be modified
+ * accordingly.
+ *
+ * @param parent parent account the account should be added to
+ * @param account the account to be added
+ */
+ void addAccount(MyMoneyAccount& parent, MyMoneyAccount& account);
+
+ /**
+ * Adds an institution to the storage. A
+ * respective institution-ID will be generated within this record.
+ * The ID is stored as QString in the object passed as argument.
+ * An exception will be thrown upon error conditions.
+ *
+ * @param institution The complete institution information in a
+ * MyMoneyInstitution object
+ */
+ void addInstitution(MyMoneyInstitution& institution);
+
+ /**
+ * Adds a transaction to the file-global transaction pool. A respective
+ * transaction-ID will be generated within this record. The ID is stored
+ * as QString in the transaction object. The accounts of the referenced splits
+ * will be updated to have a reference to the transaction just added.
+ *
+ * @param transaction reference to the transaction
+ * @param skipAccountUpdate if set, the transaction lists of the accounts
+ * referenced in the splits are not updated. This is used for
+ * bulk loading a lot of transactions but not during normal operation
+ */
+ void addTransaction(MyMoneyTransaction& transaction, const bool skipAccountUpdate = false);
+
+ /**
+ * Modifies an already existing account in the file global account pool.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param account reference to the new account information
+ * @param skipCheck if @p true, skips the built in consistency check for
+ * the object to be updated. Do not set this parameter
+ * to true. This is only used for the MyMoneyFile::consistencyCheck()
+ * procedure to be able to reload accounts. The default
+ * setting of this parameter is @p false.
+ */
+ void modifyAccount(const MyMoneyAccount& account, const bool skipCheck = false);
+
+ /**
+ * Modifies an already existing institution in the file global
+ * institution pool.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param institution The complete new institution information
+ */
+ void modifyInstitution(const MyMoneyInstitution& institution);
+
+ /**
+ * This method is used to update a specific transaction in the
+ * transaction pool of the MyMoneyFile object
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param transaction reference to transaction to be changed
+ */
+ void modifyTransaction(const MyMoneyTransaction& transaction);
+
+ /**
+ * This method re-parents an existing account
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param account MyMoneyAccount reference to account to be re-parented
+ * @param parent MyMoneyAccount reference to new parent account
+ */
+ void reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent);
+
+ /**
+ * This method is used to remove a transaction from the transaction
+ * pool (journal).
+ *
+ * @param transaction const reference to transaction to be deleted
+ */
+ void removeTransaction(const MyMoneyTransaction& transaction);
+
+ /**
+ * Deletes an existing account from the file global account pool
+ * This method only allows to remove accounts that are not
+ * referenced by any split. Use moveSplits() to move splits
+ * to another account. An exception is thrown in case of a
+ * problem.
+ *
+ * @param account reference to the account to be deleted.
+ */
+ void removeAccount(const MyMoneyAccount& account);
+
+ /**
+ * Deletes an existing institution from the file global institution pool
+ * Also modifies the accounts that reference this institution as
+ * their institution.
+ *
+ * @param institution institution to be deleted.
+ */
+ void removeInstitution(const MyMoneyInstitution& institution);
+
+ /**
+ * This method is used to extract a transaction from the file global
+ * transaction pool through an id. In case of an invalid id, an
+ * exception will be thrown.
+ *
+ * @param id id of transaction as QString.
+ * @return reference to the requested transaction
+ */
+ const MyMoneyTransaction transaction(const QString& id) const;
+
+ /**
+ * This method is used to extract a transaction from the file global
+ * transaction pool through an index into an account.
+ *
+ * @param account id of the account as QString
+ * @param idx number of transaction in this account
+ * @return reference to MyMoneyTransaction object
+ */
+ const MyMoneyTransaction transaction(const QString& account, const int idx) const;
+
+ /**
+ * This method is used to determince, if the account with the
+ * given ID is referenced by any split in m_transactionList.
+ *
+ * @param id id of the account to be checked for
+ * @return true if account is referenced, false otherwise
+ */
+ bool hasActiveSplits(const QString& id) const;
+
+ /**
+ * This method is used to return the actual balance of an account
+ * without it's sub-ordinate accounts. If a @p date is presented,
+ * the balance at the beginning of this date (not including any
+ * transaction on this date) is returned. Otherwise all recorded
+ * transactions are included in the balance.
+ *
+ * @param id id of the account in question
+ * @param date return balance for specific date
+ * @return balance of the account as MyMoneyMoney object
+ */
+ const MyMoneyMoney balance(const QString& id, const QDate& date = QDate()) const;
+
+ /**
+ * This method is used to return the actual balance of an account
+ * including it's sub-ordinate accounts. If a @p date is presented,
+ * the balance at the beginning of this date (not including any
+ * transaction on this date) is returned. Otherwise all recorded
+ * transactions are included in the balance.
+ *
+ * @param id id of the account in question
+ * @param date return balance for specific date
+ * @return balance of the account as MyMoneyMoney object
+ */
+ const MyMoneyMoney totalBalance(const QString& id, const QDate& date = QDate()) const;
+
+ /**
+ * Returns the institution of a given ID
+ *
+ * @param id id of the institution to locate
+ * @return MyMoneyInstitution object filled with data. If the institution
+ * could not be found, an exception will be thrown
+ */
+ const MyMoneyInstitution institution(const QString& id) const;
+
+ /**
+ * This method returns an indicator if the storage object has been
+ * changed after it has last been saved to permanent storage.
+ *
+ * @return true if changed, false if not
+ */
+ bool dirty(void) const { return m_dirty; }
+
+ /**
+ * This method can be used by an external object to force the
+ * storage object to be dirty. This is used e.g. when an upload
+ * to an external destination failed but the previous storage
+ * to a local disk was ok.
+ */
+ void setDirty(void) { m_dirty = true; };
+
+ /**
+ * This method returns a list of the institutions
+ * inside a MyMoneyFile object
+ *
+ * @return QMap containing the institution information
+ */
+ const QValueList<MyMoneyInstitution> institutionList(void) const;
+
+ /**
+ * This method returns a list of accounts inside the storage object.
+ *
+ * @param list reference to QValueList receiving the account objects
+ *
+ * @note The standard accounts will not be returned
+ */
+ void accountList(QValueList<MyMoneyAccount>& list) const;
+
+ /**
+ * This method is used to pull a list of transactions from the file
+ * global transaction pool. It returns all those transactions
+ * that match the filter passed as argument. If the filter is empty,
+ * the whole journal will be returned.
+ * The list returned is sorted according to the transactions posting date.
+ * If more than one transaction exists for the same date, the order among
+ * them is undefined.
+ *
+ * The @p list will be cleared by this method.
+ *
+ * @param list reference to list
+ * @param filter MyMoneyTransactionFilter object with the match criteria
+ *
+ * @return set of transactions in form of a QValueList<MyMoneyTransaction>
+ */
+ void transactionList(QValueList<MyMoneyTransaction>& list, MyMoneyTransactionFilter& filter) const;
+
+ /**
+ * This method is used to pull a list of transactions from the file
+ * global transaction pool. It returns all those transactions
+ * that match the filter passed as argument. If the filter is empty,
+ * the whole journal will be returned.
+ * The list returned is sorted according to the transactions posting date.
+ * If more than one transaction exists for the same date, the order among
+ * them is undefined.
+ *
+ * The @p list will be cleared by this method.
+ *
+ * @param list reference to list
+ * @param filter MyMoneyTransactionFilter object with the match criteria
+ *
+ * @return set of transactions in form of a QValueList<QPair<MyMoneyTransaction,MyMoneySplit> >
+ */
+ void transactionList(QValueList< QPair<MyMoneyTransaction, MyMoneySplit> >& list, MyMoneyTransactionFilter& filter) const;
+
+ /**
+ * Compatibility interface for the previous method.
+ */
+ const QValueList<MyMoneyTransaction> transactionList(MyMoneyTransactionFilter& filter) const;
+
+ /**
+ * This method returns whether a given transaction is already in memory, to avoid
+ * reloading it from the database
+ */
+ bool isDuplicateTransaction(const QString& id) const { return m_transactionKeys.contains(id); }
+
+ /**
+ * This method returns the number of transactions currently known to file
+ * in the range 0..MAXUINT
+ *
+ * @param account QString reference to account id. If account is empty
+ + all transactions (the journal) will be counted. If account
+ * is not empty it returns the number of transactions
+ * that have splits in this account.
+ *
+ * @return number of transactions in journal/account
+ */
+ unsigned int transactionCount(const QString& account = QString()) const;
+
+ const QMap<QString, unsigned long> transactionCountMap(void) const;
+
+ /**
+ * This method returns the number of institutions currently known to file
+ * in the range 0..MAXUINT
+ *
+ * @return number of institutions known to file
+ */
+ unsigned int institutionCount(void) const;
+
+ /**
+ * This method returns the number of accounts currently known to file
+ * in the range 0..MAXUINT
+ *
+ * @return number of accounts currently known inside a MyMoneyFile object
+ */
+ unsigned int accountCount(void) const;
+
+ /**
+ * This method is used to return the standard liability account
+ * @return MyMoneyAccount liability account(group)
+ */
+ const MyMoneyAccount liability(void) const { return account(STD_ACC_LIABILITY); };
+
+ /**
+ * This method is used to return the standard asset account
+ * @return MyMoneyAccount asset account(group)
+ */
+ const MyMoneyAccount asset(void) const { return account(STD_ACC_ASSET); };
+
+ /**
+ * This method is used to return the standard expense account
+ * @return MyMoneyAccount expense account(group)
+ */
+ const MyMoneyAccount expense(void) const { return account(STD_ACC_EXPENSE); };
+
+ /**
+ * This method is used to return the standard income account
+ * @return MyMoneyAccount income account(group)
+ */
+ const MyMoneyAccount income(void) const { return account(STD_ACC_INCOME); };
+
+ /**
+ * This method is used to return the standard equity account
+ * @return MyMoneyAccount equity account(group)
+ */
+ const MyMoneyAccount equity(void) const { return account(STD_ACC_EQUITY); };
+
+ virtual void loadAccounts(const QMap<QString, MyMoneyAccount>& acc);
+ virtual void loadTransactions(const QMap<QString, MyMoneyTransaction>& map);
+ virtual void loadInstitutions(const QMap<QString, MyMoneyInstitution>& map);
+ virtual void loadPayees(const QMap<QString, MyMoneyPayee>& map);
+ virtual void loadSchedules(const QMap<QString, MyMoneySchedule>& map);
+ virtual void loadSecurities(const QMap<QString, MyMoneySecurity>& map);
+ virtual void loadCurrencies(const QMap<QString, MyMoneySecurity>& map);
+ virtual void loadPrices(const MyMoneyPriceList& list);
+
+ virtual void loadAccountId(const unsigned long id);
+ virtual void loadTransactionId(const unsigned long id);
+ virtual void loadPayeeId(const unsigned long id);
+ virtual void loadInstitutionId(const unsigned long id);
+ virtual void loadScheduleId(const unsigned long id);
+ virtual void loadSecurityId(const unsigned long id);
+ virtual void loadReportId(const unsigned long id);
+ virtual void loadBudgetId(const unsigned long id);
+
+ virtual unsigned long accountId(void) const { return m_nextAccountID; };
+ virtual unsigned long transactionId(void) const { return m_nextTransactionID; };
+ virtual unsigned long payeeId(void) const { return m_nextPayeeID; };
+ virtual unsigned long institutionId(void) const { return m_nextInstitutionID; };
+ virtual unsigned long scheduleId(void) const { return m_nextScheduleID; };
+ virtual unsigned long securityId(void) const { return m_nextSecurityID; };
+ virtual unsigned long reportId(void) const { return m_nextReportID; };
+ virtual unsigned long budgetId(void) const { return m_nextBudgetID; };
+
+
+ /**
+ * This method is used to extract a value from
+ * KeyValueContainer. For details see MyMoneyKeyValueContainer::value().
+ *
+ * @param key const reference to QString containing the key
+ * @return QString containing the value
+ */
+ const QString value(const QString& key) const;
+
+ /**
+ * This method is used to set a value in the
+ * KeyValueContainer. For details see MyMoneyKeyValueContainer::setValue().
+ *
+ * @param key const reference to QString containing the key
+ * @param val const reference to QString containing the value
+ */
+ void setValue(const QString& key, const QString& val);
+
+ /**
+ * This method is used to delete a key-value-pair from the
+ * KeyValueContainer identified by the parameter
+ * @p key. For details see MyMoneyKeyValueContainer::deletePair().
+ *
+ * @param key const reference to QString containing the key
+ */
+ void deletePair(const QString& key);
+
+ // documented in IMyMoneySerialize base class
+ const QMap<QString, QString> pairs(void) const;
+
+ // documented in IMyMoneySerialize base class
+ void setPairs(const QMap<QString, QString>& list);
+
+ /**
+ * This method is used to add a scheduled transaction to the engine.
+ * It must be sure, that the id of the object is not filled. When the
+ * method returns to the caller, the id will be filled with the
+ * newly created object id value.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param sched reference to the MyMoneySchedule object
+ */
+ void addSchedule(MyMoneySchedule& sched);
+
+ /**
+ * This method is used to modify an existing MyMoneySchedule
+ * object. Therefor, the id attribute of the object must be set.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param sched const reference to the MyMoneySchedule object to be updated
+ */
+ void modifySchedule(const MyMoneySchedule& sched);
+
+ /**
+ * This method is used to remove an existing MyMoneySchedule object
+ * from the engine. The id attribute of the object must be set.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param sched const reference to the MyMoneySchedule object to be updated
+ */
+ void removeSchedule(const MyMoneySchedule& sched);
+
+ /**
+ * This method is used to retrieve a single MyMoneySchedule object.
+ * The id of the object must be supplied in the parameter @p id.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param id QString containing the id of the MyMoneySchedule object
+ * @return MyMoneySchedule object
+ */
+ const MyMoneySchedule schedule(const QString& id) const;
+
+ /**
+ * This method is used to create a new security object. The ID will be created
+ * automatically. The object passed with the parameter @p security is modified
+ * to contain the assigned id.
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param security MyMoneySecurity filled with data
+ */
+ virtual void addSecurity(MyMoneySecurity& security);
+
+ /**
+ * This method is used to modify an existing MyMoneySchedule
+ * object.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param security reference to the MyMoneySecurity object to be updated
+ */
+ void modifySecurity(const MyMoneySecurity& security);
+
+ /**
+ * This method is used to remove an existing MyMoneySecurity object
+ * from the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param security reference to the MyMoneySecurity object to be removed
+ */
+ void removeSecurity(const MyMoneySecurity& security);
+
+ /**
+ * This method is used to retrieve a single MyMoneySchedule object.
+ * The id of the object must be supplied in the parameter @p id.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param id QString containing the id of the MyMoneySchedule object
+ * @return MyMoneySchedule object
+ */
+ const MyMoneySecurity security(const QString& id) const;
+
+
+ /**
+ * This method returns a list of security objects that the engine has
+ * knowledge of.
+ */
+ const QValueList<MyMoneySecurity> securityList(void) const;
+
+ /**
+ * This method is used to add a new currency object to the engine.
+ * The ID of the object is the trading symbol, so there is no need for an additional
+ * ID since the symbol is guaranteed to be unique.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param currency reference to the MyMoneyCurrency object
+ */
+ void addCurrency(const MyMoneySecurity& currency);
+
+ /**
+ * This method is used to modify an existing MyMoneyCurrency
+ * object.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param currency reference to the MyMoneyCurrency object
+ */
+ void modifyCurrency(const MyMoneySecurity& currency);
+
+ /**
+ * This method is used to remove an existing MyMoneyCurrency object
+ * from the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param currency reference to the MyMoneyCurrency object
+ */
+ void removeCurrency(const MyMoneySecurity& currency);
+
+ /**
+ * This method is used to retrieve a single MyMoneySchedule object.
+ * The id of the object must be supplied in the parameter @p id.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param id QString containing the id of the MyMoneySchedule object
+ * @return MyMoneySchedule object
+ */
+ const MyMoneySecurity currency(const QString& id) const;
+
+ /**
+ * This method is used to retrieve the list of all currencies
+ * known to the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @return QValueList of all MyMoneyCurrency objects.
+ */
+ const QValueList<MyMoneySecurity> currencyList(void) const;
+
+ /**
+ * This method is used to extract a list of scheduled transactions
+ * according to the filter criteria passed as arguments.
+ *
+ * @param accountId only search for scheduled transactions that reference
+ * accound @p accountId. If accountId is the empty string,
+ * this filter is off. Default is @p QString().
+ * @param type only schedules of type @p type are searched for.
+ * See MyMoneySchedule::typeE for details.
+ * Default is MyMoneySchedule::TYPE_ANY
+ * @param occurence only schedules of occurence type @p occurence are searched for.
+ * See MyMoneySchedule::occurenceE for details.
+ * Default is MyMoneySchedule::OCCUR_ANY
+ * @param paymentType only schedules of payment method @p paymentType
+ * are searched for.
+ * See MyMoneySchedule::paymentTypeE for details.
+ * Default is MyMoneySchedule::STYPE_ANY
+ * @param startDate only schedules with payment dates after @p startDate
+ * are searched for. Default is all dates (QDate()).
+ * @param endDate only schedules with payment dates ending prior to @p endDate
+ * are searched for. Default is all dates (QDate()).
+ * @param overdue if true, only those schedules that are overdue are
+ * searched for. Default is false (all schedules will be returned).
+ *
+ * @return const QValueList<MyMoneySchedule> list of schedule objects.
+ */
+ const QValueList<MyMoneySchedule> scheduleList(const QString& accountId = QString(),
+ const MyMoneySchedule::typeE type = MyMoneySchedule::TYPE_ANY,
+ const MyMoneySchedule::occurenceE occurence = MyMoneySchedule::OCCUR_ANY,
+ const MyMoneySchedule::paymentTypeE paymentType = MyMoneySchedule::STYPE_ANY,
+ const QDate& startDate = QDate(),
+ const QDate& endDate = QDate(),
+ const bool overdue = false) const;
+
+ const QValueList<MyMoneySchedule> scheduleListEx( int scheduleTypes,
+ int scheduleOcurrences,
+ int schedulePaymentTypes,
+ QDate startDate,
+ const QStringList& accounts=QStringList()) const;
+
+ /**
+ * This method is used to retrieve the list of all reports
+ * known to the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @return QValueList of all MyMoneyReport objects.
+ */
+ const QValueList<MyMoneyReport> reportList( void ) const;
+
+ /**
+ * This method is used to add a new report to the engine.
+ * It must be sure, that the id of the object is not filled. When the
+ * method returns to the caller, the id will be filled with the
+ * newly created object id value.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param report reference to the MyMoneyReport object
+ */
+ void addReport( MyMoneyReport& report );
+
+ /**
+ * This method is used to load a set of reports into the engine. This is
+ * used when loading from storage, and an ID is already known.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param reports reference to the map of MyMoneyReport objects
+ */
+ void loadReports( const QMap<QString, MyMoneyReport>& reports );
+
+ /**
+ * This method is used to modify an existing MyMoneyReport
+ * object. Therefor, the id attribute of the object must be set.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param report const reference to the MyMoneyReport object to be updated
+ */
+ void modifyReport( const MyMoneyReport& report );
+
+ /**
+ * This method returns the number of reports currently known to file
+ * in the range 0..MAXUINT
+ *
+ * @return number of reports known to file
+ */
+ unsigned countReports(void) const;
+
+ /**
+ * This method is used to retrieve a single MyMoneyReport object.
+ * The id of the object must be supplied in the parameter @p id.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param id QString containing the id of the MyMoneyReport object
+ * @return MyMoneyReport object
+ */
+ const MyMoneyReport report( const QString& id ) const;
+
+ /**
+ * This method is used to remove an existing MyMoneyReport object
+ * from the engine. The id attribute of the object must be set.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param report const reference to the MyMoneyReport object to be updated
+ */
+ void removeReport(const MyMoneyReport& report);
+
+ /**
+ * This method is used to retrieve the list of all budgets
+ * known to the engine.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @return QValueList of all MyMoneyBudget objects.
+ */
+ const QValueList<MyMoneyBudget> budgetList( void ) const;
+
+ /**
+ * This method is used to add a new budget to the engine.
+ * It must be sure, that the id of the object is not filled. When the
+ * method returns to the caller, the id will be filled with the
+ * newly created object id value.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param budget reference to the MyMoneyBudget object
+ */
+ void addBudget( MyMoneyBudget& budget );
+
+ /**
+ * This method is used to load a set of budgets into the engine. This is
+ * used when loading from storage, and an ID is already known.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param budgets reference to the map of MyMoneyBudget object
+ */
+ void loadBudgets(const QMap<QString, MyMoneyBudget>& budgets);
+
+ /**
+ * This method is used to retrieve the id to a corresponding
+ * name of a budget
+ * An exception will be thrown upon error conditions.
+ *
+ * @param budget QString reference to name of budget
+ *
+ * @return MyMoneyBudget reference to object of budget
+ */
+ const MyMoneyBudget budgetByName(const QString& budget) const;
+
+ /**
+ * This method is used to modify an existing MyMoneyBudget
+ * object. Therefore, the id attribute of the object must be set.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param budget const reference to the MyMoneyBudget object to be updated
+ */
+ void modifyBudget( const MyMoneyBudget& budget );
+
+ /**
+ * This method returns the number of budgets currently known to file
+ * in the range 0..MAXUINT
+ *
+ * @return number of budgets known to file
+ */
+ unsigned countBudgets(void) const;
+
+ /**
+ * This method is used to retrieve a single MyMoneyBudget object.
+ * The id of the object must be supplied in the parameter @p id.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param id QString containing the id of the MyMoneyBudget object
+ * @return MyMoneyBudget object
+ */
+ MyMoneyBudget budget( const QString& id ) const;
+
+ /**
+ * This method is used to remove an existing MyMoneyBudget object
+ * from the engine. The id attribute of the object must be set.
+ *
+ * An exception will be thrown upon erronous situations.
+ *
+ * @param budget const reference to the MyMoneyBudget object to be updated
+ */
+ void removeBudget(const MyMoneyBudget& budget);
+
+
+ /**
+ * This method adds/replaces a price to/from the price list
+ */
+ void addPrice(const MyMoneyPrice& price);
+
+ /**
+ * This method removes a price from the price list
+ */
+ void removePrice(const MyMoneyPrice& price);
+
+ /**
+ * This method retrieves a price from the price list.
+ * If @p date is inValid, QDate::currentDate() is assumed.
+ */
+ const MyMoneyPrice price(const QString& fromId, const QString& toId, const QDate& date, const bool exactDate) const;
+
+ /**
+ * This method returns a list of all price entries.
+ */
+ const MyMoneyPriceList priceList(void) const;
+
+ /**
+ * Clear all internal caches (used internally for performance measurements)
+ */
+ void clearCache(void);
+
+ /**
+ * This method checks, if the given @p object is referenced
+ * by another engine object.
+ *
+ * @param obj const reference to object to be checked
+ * @param skipCheck QBitArray with ReferenceCheckBits set for which
+ * the check should be skipped
+ *
+ * @retval false @p object is not referenced
+ * @retval true @p institution is referenced
+ */
+ bool isReferenced(const MyMoneyObject& obj, const MyMoneyFileBitArray& skipCheck = MyMoneyFileBitArray()) const;
+
+ /**
+ * This method recalculates the balances of all accounts
+ * based on the transactions stored in the engine.
+ */
+ void rebuildAccountBalances(void);
+
+ virtual void startTransaction(void);
+ virtual bool commitTransaction(void);
+ virtual void rollbackTransaction(void);
+
+protected:
+ void removeReferences(const QString& id);
+
+private:
+
+ static const int INSTITUTION_ID_SIZE = 6;
+ static const int ACCOUNT_ID_SIZE = 6;
+ static const int TRANSACTION_ID_SIZE = 18;
+ static const int PAYEE_ID_SIZE = 6;
+ static const int SCHEDULE_ID_SIZE = 6;
+ static const int SECURITY_ID_SIZE = 6;
+ static const int REPORT_ID_SIZE = 6;
+ static const int BUDGET_ID_SIZE = 6;
+
+ /**
+ * This method is used to set the dirty flag and update the
+ * date of the last modification.
+ */
+ void touch(void);
+
+ /**
+ * This method is used to invalidate the cached balance for
+ * the selected account and all it's parents.
+ *
+ * @param id id of the account in question
+ */
+ void invalidateBalanceCache(const QString& id);
+
+ /**
+ * This member variable keeps the User information.
+ * @see setUser()
+ */
+ MyMoneyPayee m_user;
+
+ /**
+ * The member variable m_nextInstitutionID keeps the number that will be
+ * assigned to the next institution created. It is maintained by
+ * nextInstitutionID().
+ */
+ unsigned long m_nextInstitutionID;
+
+ /**
+ * The member variable m_nextAccountID keeps the number that will be
+ * assigned to the next institution created. It is maintained by
+ * nextAccountID().
+ */
+ unsigned long m_nextAccountID;
+
+ /**
+ * The member variable m_nextTransactionID keeps the number that will be
+ * assigned to the next transaction created. It is maintained by
+ * nextTransactionID().
+ */
+ unsigned long m_nextTransactionID;
+
+ /**
+ * The member variable m_nextPayeeID keeps the number that will be
+ * assigned to the next payee created. It is maintained by
+ * nextPayeeID()
+ */
+ unsigned long m_nextPayeeID;
+
+ /**
+ * The member variable m_nextScheduleID keeps the number that will be
+ * assigned to the next schedule created. It is maintained by
+ * nextScheduleID()
+ */
+ unsigned long m_nextScheduleID;
+
+ /**
+ * The member variable m_nextSecurityID keeps the number that will be
+ * assigned to the next security object created. It is maintained by
+ * nextSecurityID()
+ */
+ unsigned long m_nextSecurityID;
+
+ unsigned long m_nextReportID;
+
+ /**
+ * The member variable m_nextBudgetID keeps the number that will be
+ * assigned to the next budget object created. It is maintained by
+ * nextBudgetID()
+ */
+ unsigned long m_nextBudgetID;
+
+ /**
+ * The member variable m_institutionList is the container for the
+ * institutions known within this file.
+ */
+ MyMoneyMap<QString, MyMoneyInstitution> m_institutionList;
+
+ /**
+ * The member variable m_accountList is the container for the accounts
+ * known within this file.
+ */
+ MyMoneyMap<QString, MyMoneyAccount> m_accountList;
+
+ /**
+ * The member variable m_balanceCache is the container for the
+ * accounts actual balance
+ */
+ mutable QMap<QString, MyMoneyBalanceCacheItem> m_balanceCache;
+
+ /**
+ * This member keeps the date for which the m_balanceCache member
+ * is valid. In case the whole cache is invalid it is set to
+ * QDate().
+ */
+ mutable QDate m_balanceCacheDate;
+
+ /**
+ * The member variable m_transactionList is the container for all
+ * transactions within this file.
+ * @see m_transactionKeys
+ */
+ MyMoneyMap<QString, MyMoneyTransaction> m_transactionList;
+
+ /**
+ * The member variable m_transactionKeys is used to convert
+ * transaction id's into the corresponding key used in m_transactionList.
+ * @see m_transactionList;
+ */
+ MyMoneyMap<QString, QString> m_transactionKeys;
+
+ /**
+ * A list containing all the payees that have been used
+ */
+ MyMoneyMap<QString, MyMoneyPayee> m_payeeList;
+
+ /**
+ * A list containing all the scheduled transactions
+ */
+ MyMoneyMap<QString, MyMoneySchedule> m_scheduleList;
+
+ /**
+ * A list containing all the security information objects. Each object
+ * can represent a stock, bond, or mutual fund. It contains a price
+ * history that a user can add entries to. The price history will be used
+ * to determine the cost basis for sales, as well as the source of
+ * information for reports in a security account.
+ */
+ MyMoneyMap<QString, MyMoneySecurity> m_securitiesList;
+
+ /**
+ * A list containing all the currency information objects.
+ */
+ MyMoneyMap<QString, MyMoneySecurity> m_currencyList;
+
+ MyMoneyMap<QString, MyMoneyReport> m_reportList;
+
+ /**
+ * A list containing all the budget information objects.
+ */
+ MyMoneyMap<QString, MyMoneyBudget> m_budgetList;
+
+ MyMoneyMap<MyMoneySecurityPair, MyMoneyPriceEntries> m_priceList;
+
+ /**
+ * This member signals if the file has been modified or not
+ */
+ bool m_dirty;
+
+ /**
+ * This member variable keeps the creation date of this MyMoneySeqAccessMgr
+ * object. It is set during the constructor and can only be modified using
+ * the stream read operator.
+ */
+ QDate m_creationDate;
+
+ /**
+ * This member variable keeps the date of the last modification of
+ * the MyMoneySeqAccessMgr object.
+ */
+ QDate m_lastModificationDate;
+
+ /**
+ * This member variable contains the current fix level of application
+ * data files. (see kmymoneyview.cpp)
+ */
+ unsigned int m_currentFixVersion;
+ /**
+ * This member variable contains the current fix level of the
+ * presently open data file. (see kmymoneyview.cpp)
+ */
+ unsigned int m_fileFixVersion;
+ /**
+ * This method is used to get the next valid ID for a institution
+ * @return id for a institution
+ */
+ QString nextInstitutionID(void);
+
+ /**
+ * This method is used to get the next valid ID for an account
+ * @return id for an account
+ */
+ QString nextAccountID(void);
+
+ /**
+ * This method is used to get the next valid ID for a transaction
+ * @return id for a transaction
+ */
+ QString nextTransactionID(void);
+
+ /**
+ * This method is used to get the next valid ID for a payee
+ * @return id for a payee
+ */
+ QString nextPayeeID(void);
+
+ /**
+ * This method is used to get the next valid ID for a scheduled transaction
+ * @return id for a scheduled transaction
+ */
+ QString nextScheduleID(void);
+
+ /**
+ * This method is used to get the next valid ID for an security object.
+ * @return id for an security object
+ */
+ QString nextSecurityID(void);
+
+ QString nextReportID(void);
+
+ /**
+ * This method is used to get the next valid ID for a budget object.
+ * @return id for an budget object
+ */
+ QString nextBudgetID(void);
+
+
+ /**
+ * This method re-parents an existing account
+ *
+ * An exception will be thrown upon error conditions.
+ *
+ * @param account MyMoneyAccount reference to account to be re-parented
+ * @param parent MyMoneyAccount reference to new parent account
+ * @param sendNotification if true, notifications with the ids
+ * of all modified objects are send
+ */
+ void reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent, const bool sendNotification);
+ /**
+ * This method will close a database and log the use roff
+ */
+ void close(void) {}
+
+ /**
+ * This member variable is set when all transactions have been read from the database.
+ * This is would be probably the case when doing, for e.g., a full report,
+ * or after some types of transaction search which cannot be easily implemented in SQL
+ */
+ bool m_transactionListFull;
+};
+#endif
diff --git a/kmymoney2/mymoney/storage/mymoneyseqaccessmgrtest.cpp b/kmymoney2/mymoney/storage/mymoneyseqaccessmgrtest.cpp
new file mode 100644
index 0000000..09bf791
--- /dev/null
+++ b/kmymoney2/mymoney/storage/mymoneyseqaccessmgrtest.cpp
@@ -0,0 +1,1705 @@
+/***************************************************************************
+ mymoneyseqaccessmgrtest.cpp
+ -------------------
+ copyright : (C) 2002 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "mymoneyseqaccessmgrtest.h"
+#include <iostream>
+
+MyMoneySeqAccessMgrTest::MyMoneySeqAccessMgrTest()
+{
+}
+
+
+void MyMoneySeqAccessMgrTest::setUp()
+{
+ m = new MyMoneySeqAccessMgr;
+ MyMoneyFile* file = MyMoneyFile::instance();
+ file->attachStorage(m);
+ m->startTransaction();
+}
+
+void MyMoneySeqAccessMgrTest::tearDown()
+{
+ m->commitTransaction();
+ MyMoneyFile* file = MyMoneyFile::instance();
+ file->detachStorage(m);
+ delete m;
+}
+
+void MyMoneySeqAccessMgrTest::testEmptyConstructor()
+{
+ MyMoneyPayee user = m->user();
+
+ CPPUNIT_ASSERT(user.name().isEmpty());
+ CPPUNIT_ASSERT(user.address().isEmpty());
+ CPPUNIT_ASSERT(user.city().isEmpty());
+ CPPUNIT_ASSERT(user.state().isEmpty());
+ CPPUNIT_ASSERT(user.postcode().isEmpty());
+ CPPUNIT_ASSERT(user.telephone().isEmpty());
+ CPPUNIT_ASSERT(user.email().isEmpty());
+ CPPUNIT_ASSERT(m->m_nextInstitutionID == 0);
+ CPPUNIT_ASSERT(m->m_nextAccountID == 0);
+ CPPUNIT_ASSERT(m->m_nextTransactionID == 0);
+ CPPUNIT_ASSERT(m->m_nextPayeeID == 0);
+ CPPUNIT_ASSERT(m->m_nextScheduleID == 0);
+ CPPUNIT_ASSERT(m->m_nextReportID == 0);
+ CPPUNIT_ASSERT(m->m_institutionList.count() == 0);
+ CPPUNIT_ASSERT(m->m_accountList.count() == 5);
+ CPPUNIT_ASSERT(m->m_transactionList.count() == 0);
+ CPPUNIT_ASSERT(m->m_transactionKeys.count() == 0);
+ CPPUNIT_ASSERT(m->m_payeeList.count() == 0);
+ CPPUNIT_ASSERT(m->m_scheduleList.count() == 0);
+
+ CPPUNIT_ASSERT(m->m_dirty == false);
+ CPPUNIT_ASSERT(m->m_creationDate == QDate::currentDate());
+
+ CPPUNIT_ASSERT(m->liability().name() == "Liability");
+ CPPUNIT_ASSERT(m->asset().name() == "Asset");
+ CPPUNIT_ASSERT(m->expense().name() == "Expense");
+ CPPUNIT_ASSERT(m->income().name() == "Income");
+ CPPUNIT_ASSERT(m->equity().name() == "Equity");
+}
+
+void MyMoneySeqAccessMgrTest::testSetFunctions() {
+ MyMoneyPayee user = m->user();
+
+ m->m_dirty = false;
+ user.setName("Name");
+ m->setUser(user);
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->dirty() == true);
+ m->m_dirty = false;
+ user.setAddress("Street");
+ m->setUser(user);
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->dirty() == true);
+ m->m_dirty = false;
+ user.setCity("Town");
+ m->setUser(user);
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->dirty() == true);
+ m->m_dirty = false;
+ user.setState("County");
+ m->setUser(user);
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->dirty() == true);
+ m->m_dirty = false;
+ user.setPostcode("Postcode");
+ m->setUser(user);
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->dirty() == true);
+ m->m_dirty = false;
+ user.setTelephone("Telephone");
+ m->setUser(user);
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->dirty() == true);
+ m->m_dirty = false;
+ user.setEmail("Email");
+ m->setUser(user);
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->dirty() == true);
+ m->m_dirty = false;
+ m->setValue("key", "value");
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->dirty() == true);
+
+ user = m->user();
+ CPPUNIT_ASSERT(user.name() == "Name");
+ CPPUNIT_ASSERT(user.address() == "Street");
+ CPPUNIT_ASSERT(user.city() == "Town");
+ CPPUNIT_ASSERT(user.state() == "County");
+ CPPUNIT_ASSERT(user.postcode() == "Postcode");
+ CPPUNIT_ASSERT(user.telephone() == "Telephone");
+ CPPUNIT_ASSERT(user.email() == "Email");
+ CPPUNIT_ASSERT(m->value("key") == "value");
+
+ m->m_dirty = false;
+ m->deletePair("key");
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->dirty() == true);
+}
+
+void MyMoneySeqAccessMgrTest::testSupportFunctions()
+{
+ CPPUNIT_ASSERT(m->nextInstitutionID() == "I000001");
+ CPPUNIT_ASSERT(m->m_nextInstitutionID == 1);
+ CPPUNIT_ASSERT(m->nextAccountID() == "A000001");
+ CPPUNIT_ASSERT(m->m_nextAccountID == 1);
+ CPPUNIT_ASSERT(m->nextTransactionID() == "T000000000000000001");
+ CPPUNIT_ASSERT(m->m_nextTransactionID == 1);
+ CPPUNIT_ASSERT(m->nextPayeeID() == "P000001");
+ CPPUNIT_ASSERT(m->m_nextPayeeID == 1);
+ CPPUNIT_ASSERT(m->nextScheduleID() == "SCH000001");
+ CPPUNIT_ASSERT(m->m_nextScheduleID == 1);
+ CPPUNIT_ASSERT(m->nextReportID() == "R000001");
+ CPPUNIT_ASSERT(m->m_nextReportID == 1);
+}
+
+void MyMoneySeqAccessMgrTest::testIsStandardAccount()
+{
+ CPPUNIT_ASSERT(m->isStandardAccount(STD_ACC_LIABILITY) == true);
+ CPPUNIT_ASSERT(m->isStandardAccount(STD_ACC_ASSET) == true);
+ CPPUNIT_ASSERT(m->isStandardAccount(STD_ACC_EXPENSE) == true);
+ CPPUNIT_ASSERT(m->isStandardAccount(STD_ACC_INCOME) == true);
+ CPPUNIT_ASSERT(m->isStandardAccount("A0002") == false);
+}
+
+void MyMoneySeqAccessMgrTest::testNewAccount() {
+ MyMoneyAccount a;
+
+ a.setName("AccountName");
+ a.setNumber("AccountNumber");
+
+ m->addAccount(a);
+ m->commitTransaction();
+ m->startTransaction();
+
+ CPPUNIT_ASSERT(m->m_nextAccountID == 1);
+ CPPUNIT_ASSERT(m->dirty() == true);
+ CPPUNIT_ASSERT(m->m_accountList.count() == 6);
+ CPPUNIT_ASSERT(m->m_accountList["A000001"].name() == "AccountName");
+}
+
+void MyMoneySeqAccessMgrTest::testAccount() {
+ testNewAccount();
+ m->m_dirty = false;
+
+ MyMoneyAccount a;
+
+ // make sure that an invalid ID causes an exception
+ try {
+ a = m->account("Unknown ID");
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->dirty() == false);
+
+ // now make sure, that a real ID works
+ try {
+ a = m->account("A000001");
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(a.name() == "AccountName");
+ CPPUNIT_ASSERT(a.id() == "A000001");
+ CPPUNIT_ASSERT(m->dirty() == true);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testAddNewAccount() {
+ testNewAccount();
+
+ MyMoneyAccount a,b;
+ b.setName("Account2");
+ b.setNumber("Acc2");
+ m->addAccount(b);
+ m->commitTransaction();
+ m->startTransaction();
+
+ m->m_dirty = false;
+
+ CPPUNIT_ASSERT(m->m_nextAccountID == 2);
+ CPPUNIT_ASSERT(m->m_accountList.count() == 7);
+
+ // try to add account to undefined account
+ try {
+ MyMoneyAccount c("UnknownID", b);
+ m->addAccount(c, a);
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+ m->commitTransaction();
+ m->startTransaction();
+
+ CPPUNIT_ASSERT(m->dirty() == false);
+ // now try to add account 1 as sub-account to account 2
+ a = m->account("A000001");
+ try {
+ CPPUNIT_ASSERT(m->m_accountList[STD_ACC_ASSET].accountList().count() == 0);
+ m->addAccount(b, a);
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->m_accountList["A000002"].accountList()[0] == "A000001");
+ CPPUNIT_ASSERT(m->m_accountList["A000002"].accountList().count() == 1);
+ CPPUNIT_ASSERT(m->m_accountList[STD_ACC_ASSET].accountList().count() == 0);
+ CPPUNIT_ASSERT(m->dirty() == true);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testAddInstitution() {
+ MyMoneyInstitution i;
+
+ i.setName("Inst Name");
+
+ m->addInstitution(i);
+ CPPUNIT_ASSERT(m->m_institutionList.count() == 1);
+ CPPUNIT_ASSERT(m->m_nextInstitutionID == 1);
+ CPPUNIT_ASSERT(m->m_institutionList["I000001"].name() == "Inst Name");
+}
+
+void MyMoneySeqAccessMgrTest::testInstitution() {
+ testAddInstitution();
+ MyMoneyInstitution i;
+
+ m->m_dirty = false;
+
+ // try to find unknown institution
+ try {
+ i = m->institution("Unknown ID");
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ CPPUNIT_ASSERT(m->dirty() == false);
+
+ // now try to find real institution
+ try {
+ i = m->institution("I000001");
+ CPPUNIT_ASSERT(i.name() == "Inst Name");
+ CPPUNIT_ASSERT(m->dirty() == false);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testAccount2Institution() {
+ testAddInstitution();
+ testAddNewAccount();
+
+ MyMoneyInstitution i;
+ MyMoneyAccount a, b;
+
+ try {
+ i = m->institution("I000001");
+ a = m->account("A000001");
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ m->m_dirty = false;
+
+ // try to add to a false institution
+ MyMoneyInstitution fake("Unknown ID", i);
+ a.setInstitutionId(fake.id());
+ try {
+ m->modifyAccount(a);
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+ m->commitTransaction();
+ m->startTransaction();
+
+ CPPUNIT_ASSERT(m->dirty() == false);
+ // now try to do it with a real institution
+ try {
+ CPPUNIT_ASSERT(i.accountList().count() == 0);
+ a.setInstitutionId(i.id());
+ m->modifyAccount(a);
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->dirty() == true);
+ CPPUNIT_ASSERT(a.institutionId() == i.id());
+ b = m->account("A000001");
+ CPPUNIT_ASSERT(b.institutionId() == i.id());
+ CPPUNIT_ASSERT(i.accountList().count() == 0);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testModifyAccount() {
+ testAccount2Institution();
+
+ // test the OK case
+ MyMoneyAccount a = m->account("A000001");
+ a.setName("New account name");
+ m->m_dirty = false;
+ try {
+ m->modifyAccount(a);
+ m->commitTransaction();
+ m->startTransaction();
+ MyMoneyAccount b = m->account("A000001");
+ CPPUNIT_ASSERT(b.parentAccountId() == a.parentAccountId());
+ CPPUNIT_ASSERT(b.name() == "New account name");
+ CPPUNIT_ASSERT(m->dirty() == true);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ // modify institution to unknown id
+ MyMoneyAccount c("Unknown ID", a);
+ m->m_dirty = false;
+ try {
+ m->modifyAccount(c);
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ // use different account type
+ MyMoneyAccount d;
+ d.setAccountType(MyMoneyAccount::CreditCard);
+ MyMoneyAccount f("A000001", d);
+ try {
+ m->modifyAccount(f);
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ // use different parent
+ a.setParentAccountId("A000002");
+ try {
+ m->modifyAccount(c);
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testModifyInstitution() {
+ testAddInstitution();
+ MyMoneyInstitution i = m->institution("I000001");
+
+ m->m_dirty = false;
+ i.setName("New inst name");
+ try {
+ m->modifyInstitution(i);
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->dirty() == true);
+ i = m->institution("I000001");
+ CPPUNIT_ASSERT(i.name() == "New inst name");
+
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ // try to modify an institution that does not exist
+ MyMoneyInstitution f("Unknown ID", i);
+ try {
+ m->modifyInstitution(f);
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testReparentAccount() {
+ // this one adds some accounts to the database
+ MyMoneyAccount ex1;
+ ex1.setAccountType(MyMoneyAccount::Expense);
+ MyMoneyAccount ex2;
+ ex2.setAccountType(MyMoneyAccount::Expense);
+ MyMoneyAccount ex3;
+ ex3.setAccountType(MyMoneyAccount::Expense);
+ MyMoneyAccount ex4;
+ ex4.setAccountType(MyMoneyAccount::Expense);
+ MyMoneyAccount in;
+ in.setAccountType(MyMoneyAccount::Income);
+ MyMoneyAccount ch;
+ ch.setAccountType(MyMoneyAccount::Checkings);
+
+ ex1.setName("Sales Tax");
+ ex2.setName("Sales Tax 16%");
+ ex3.setName("Sales Tax 7%");
+ ex4.setName("Grosseries");
+
+ in.setName("Salary");
+ ch.setName("My checkings account");
+
+ try {
+ m->addAccount(ex1);
+ m->addAccount(ex2);
+ m->addAccount(ex3);
+ m->addAccount(ex4);
+ m->addAccount(in);
+ m->addAccount(ch);
+
+ CPPUNIT_ASSERT(ex1.id() == "A000001");
+ CPPUNIT_ASSERT(ex2.id() == "A000002");
+ CPPUNIT_ASSERT(ex3.id() == "A000003");
+ CPPUNIT_ASSERT(ex4.id() == "A000004");
+ CPPUNIT_ASSERT(in.id() == "A000005");
+ CPPUNIT_ASSERT(ch.id() == "A000006");
+
+ MyMoneyAccount parent = m->expense();
+
+ m->addAccount(parent, ex1);
+ m->addAccount(ex1, ex2);
+ m->addAccount(parent, ex3);
+ m->addAccount(parent, ex4);
+
+ parent = m->income();
+ m->addAccount(parent, in);
+
+ parent = m->asset();
+ m->addAccount(parent, ch);
+
+ CPPUNIT_ASSERT(m->expense().accountCount() == 3);
+ CPPUNIT_ASSERT(m->account(ex1.id()).accountCount() == 1);
+ CPPUNIT_ASSERT(ex3.parentAccountId() == STD_ACC_EXPENSE);
+
+ m->reparentAccount(ex3, ex1);
+ CPPUNIT_ASSERT(m->expense().accountCount() == 2);
+ CPPUNIT_ASSERT(m->account(ex1.id()).accountCount() == 2);
+ CPPUNIT_ASSERT(ex3.parentAccountId() == ex1.id());
+ } catch (MyMoneyException *e) {
+ std::cout << std::endl << e->what() << std::endl;
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testAddTransactions() {
+ testReparentAccount();
+
+ MyMoneyAccount ch;
+ MyMoneyTransaction t1, t2;
+ MyMoneySplit s;
+
+ try {
+ // I made some money, great
+ s.setAccountId("A000006"); // Checkings
+ s.setShares(100000);
+ s.setValue(100000);
+ CPPUNIT_ASSERT(s.id().isEmpty());
+ t1.addSplit(s);
+
+ s.setId(QString()); // enable re-usage of split variable
+ s.setAccountId("A000005"); // Salary
+ s.setShares(-100000);
+ s.setValue(-100000);
+ CPPUNIT_ASSERT(s.id().isEmpty());
+ t1.addSplit(s);
+
+ t1.setPostDate(QDate(2002,5,10));
+ } catch (MyMoneyException *e) {
+ unexpectedException(e);
+ }
+
+ m->m_dirty = false;
+ try {
+ m->addTransaction(t1);
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->dirty() == true);
+ CPPUNIT_ASSERT(t1.id() == "T000000000000000001");
+ CPPUNIT_ASSERT(t1.splitCount() == 2);
+ CPPUNIT_ASSERT(m->transactionCount() == 1);
+ } catch (MyMoneyException *e) {
+ unexpectedException(e);
+ }
+
+ try {
+ // I spent some money, not so great
+ s.setId(QString()); // enable re-usage of split variable
+ s.setAccountId("A000004"); // Grosseries
+ s.setShares(10000);
+ s.setValue(10000);
+ CPPUNIT_ASSERT(s.id().isEmpty());
+ t2.addSplit(s);
+
+ s.setId(QString()); // enable re-usage of split variable
+ s.setAccountId("A000002"); // 16% sales tax
+ s.setShares(1200);
+ s.setValue(1200);
+ CPPUNIT_ASSERT(s.id().isEmpty());
+ t2.addSplit(s);
+
+ s.setId(QString()); // enable re-usage of split variable
+ s.setAccountId("A000003"); // 7% sales tax
+ s.setShares(400);
+ s.setValue(400);
+ CPPUNIT_ASSERT(s.id().isEmpty());
+ t2.addSplit(s);
+
+ s.setId(QString()); // enable re-usage of split variable
+ s.setAccountId("A000006"); // Checkings account
+ s.setShares(-11600);
+ s.setValue(-11600);
+ CPPUNIT_ASSERT(s.id().isEmpty());
+ t2.addSplit(s);
+
+ t2.setPostDate(QDate(2002,5,9));
+ } catch (MyMoneyException *e) {
+ unexpectedException(e);
+ }
+ m->m_dirty = false;
+ try {
+ m->addTransaction(t2);
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->dirty() == true);
+ CPPUNIT_ASSERT(t2.id() == "T000000000000000002");
+ CPPUNIT_ASSERT(t2.splitCount() == 4);
+ CPPUNIT_ASSERT(m->transactionCount() == 2);
+
+ QMap<QString, QString>::ConstIterator it_k;
+ QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
+ it_k = m->m_transactionKeys.begin();
+ it_t = m->m_transactionList.begin();
+
+ CPPUNIT_ASSERT((*it_k) == "2002-05-10-T000000000000000001");
+ CPPUNIT_ASSERT((*it_t).id() == "T000000000000000002");
+ ++it_k;
+ ++it_t;
+ CPPUNIT_ASSERT((*it_k) == "2002-05-09-T000000000000000002");
+ CPPUNIT_ASSERT((*it_t).id() == "T000000000000000001");
+ ++it_k;
+ ++it_t;
+ CPPUNIT_ASSERT(it_k == m->m_transactionKeys.end());
+ CPPUNIT_ASSERT(it_t == m->m_transactionList.end());
+
+ ch = m->account("A000006");
+
+ // check that the account's transaction list is updated
+ QValueList<MyMoneyTransaction> list;
+ MyMoneyTransactionFilter filter("A000006");
+ list = m->transactionList(filter);
+ CPPUNIT_ASSERT(list.size() == 2);
+
+ QValueList<MyMoneyTransaction>::ConstIterator it;
+ it = list.begin();
+ CPPUNIT_ASSERT((*it).id() == "T000000000000000002");
+ ++it;
+ CPPUNIT_ASSERT((*it).id() == "T000000000000000001");
+ ++it;
+ CPPUNIT_ASSERT(it == list.end());
+
+/* removed with MyMoneyAccount::Transaction
+ CPPUNIT_ASSERT(ch.transactionCount() == 2);
+
+ QValueList<MyMoneyAccount::Transaction>::ConstIterator it_l;
+ it_l = ch.transactionList().begin();
+ CPPUNIT_ASSERT((*it_l).transactionID() == "T000000000000000002");
+ CPPUNIT_ASSERT((*it_l).balance() == -11600);
+ ++it_l;
+
+ CPPUNIT_ASSERT((*it_l).transactionID() == "T000000000000000001");
+ CPPUNIT_ASSERT((*it_l).balance() == -11600+100000);
+
+ ++it_l;
+ CPPUNIT_ASSERT(it_l == ch.transactionList().end());
+*/
+
+ } catch (MyMoneyException *e) {
+ unexpectedException(e);
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testTransactionCount() {
+ testAddTransactions();
+ CPPUNIT_ASSERT(m->transactionCount("A000001") == 0);
+ CPPUNIT_ASSERT(m->transactionCount("A000002") == 1);
+ CPPUNIT_ASSERT(m->transactionCount("A000003") == 1);
+ CPPUNIT_ASSERT(m->transactionCount("A000004") == 1);
+ CPPUNIT_ASSERT(m->transactionCount("A000005") == 1);
+ CPPUNIT_ASSERT(m->transactionCount("A000006") == 2);
+}
+
+void MyMoneySeqAccessMgrTest::testBalance() {
+ testAddTransactions();
+
+ CPPUNIT_ASSERT(m->balance("A000001").isZero());
+ CPPUNIT_ASSERT(m->balance("A000002") == MyMoneyMoney(1200));
+ CPPUNIT_ASSERT(m->balance("A000003") == MyMoneyMoney(400));
+ CPPUNIT_ASSERT(m->totalBalance("A000001") == MyMoneyMoney(1600));
+ CPPUNIT_ASSERT(m->balance("A000006", QDate(2002,5,9)) == MyMoneyMoney(-11600));
+ CPPUNIT_ASSERT(m->balance("A000005", QDate(2002,5,10)) == MyMoneyMoney(-100000));
+ CPPUNIT_ASSERT(m->balance("A000006", QDate(2002,5,10)) == MyMoneyMoney(88400));
+}
+
+void MyMoneySeqAccessMgrTest::testModifyTransaction() {
+ testAddTransactions();
+
+ MyMoneyTransaction t = m->transaction("T000000000000000002");
+ MyMoneySplit s;
+ MyMoneyAccount ch;
+
+ // just modify simple stuff (splits)
+ CPPUNIT_ASSERT(t.splitCount() == 4);
+
+ s = t.splits()[0];
+ s.setShares(11000);
+ s.setValue(11000);
+ t.modifySplit(s);
+
+ CPPUNIT_ASSERT(t.splitCount() == 4);
+ s = t.splits()[3];
+ s.setShares(-12600);
+ s.setValue(-12600);
+ t.modifySplit(s);
+
+ try {
+ CPPUNIT_ASSERT(m->balance("A000004") == MyMoneyMoney(10000));
+ CPPUNIT_ASSERT(m->balance("A000006") == MyMoneyMoney(100000-11600));
+ CPPUNIT_ASSERT(m->totalBalance("A000001") == MyMoneyMoney(1600));
+ m->modifyTransaction(t);
+ CPPUNIT_ASSERT(m->balance("A000004") == MyMoneyMoney(11000));
+ CPPUNIT_ASSERT(m->balance("A000006") == MyMoneyMoney(100000-12600));
+ CPPUNIT_ASSERT(m->totalBalance("A000001") == MyMoneyMoney(1600));
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ // now modify the date
+ t.setPostDate(QDate(2002,5,11));
+ try {
+ m->modifyTransaction(t);
+ CPPUNIT_ASSERT(m->balance("A000004") == MyMoneyMoney(11000));
+ CPPUNIT_ASSERT(m->balance("A000006") == MyMoneyMoney(100000-12600));
+ CPPUNIT_ASSERT(m->totalBalance("A000001") == MyMoneyMoney(1600));
+
+ QMap<QString, QString>::ConstIterator it_k;
+ QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
+ it_k = m->m_transactionKeys.begin();
+ it_t = m->m_transactionList.begin();
+ CPPUNIT_ASSERT((*it_k) == "2002-05-10-T000000000000000001");
+ CPPUNIT_ASSERT((*it_t).id() == "T000000000000000001");
+ ++it_k;
+ ++it_t;
+ CPPUNIT_ASSERT((*it_k) == "2002-05-11-T000000000000000002");
+ CPPUNIT_ASSERT((*it_t).id() == "T000000000000000002");
+ ++it_k;
+ ++it_t;
+ CPPUNIT_ASSERT(it_k == m->m_transactionKeys.end());
+ CPPUNIT_ASSERT(it_t == m->m_transactionList.end());
+
+ ch = m->account("A000006");
+
+ // check that the account's transaction list is updated
+ QValueList<MyMoneyTransaction> list;
+ MyMoneyTransactionFilter filter("A000006");
+ list = m->transactionList(filter);
+ CPPUNIT_ASSERT(list.size() == 2);
+
+ QValueList<MyMoneyTransaction>::ConstIterator it;
+ it = list.begin();
+ CPPUNIT_ASSERT((*it).id() == "T000000000000000001");
+ ++it;
+ CPPUNIT_ASSERT((*it).id() == "T000000000000000002");
+ ++it;
+ CPPUNIT_ASSERT(it == list.end());
+
+/* removed with MyMoneyAccount::Transaction
+ // CPPUNIT_ASSERT(ch.transactionCount() == 2);
+
+ QValueList<MyMoneyAccount::Transaction>::ConstIterator it_l;
+ it_l = ch.transactionList().begin();
+ CPPUNIT_ASSERT((*it_l).transactionID() == "T000000000000000001");
+ CPPUNIT_ASSERT((*it_l).balance() == 100000);
+ ++it_l;
+
+ CPPUNIT_ASSERT((*it_l).transactionID() == "T000000000000000002");
+ CPPUNIT_ASSERT((*it_l).balance() == -12600+100000);
+
+ ++it_l;
+ CPPUNIT_ASSERT(it_l == ch.transactionList().end());
+*/
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+
+void MyMoneySeqAccessMgrTest::testRemoveUnusedAccount() {
+ testAccount2Institution();
+
+ MyMoneyAccount a = m->account("A000001");
+ MyMoneyInstitution i = m->institution("I000001");
+
+ m->m_dirty = false;
+ // make sure, we cannot remove the standard account groups
+ try {
+ m->removeAccount(m->liability());
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ try {
+ m->removeAccount(m->asset());
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ try {
+ m->removeAccount(m->expense());
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ try {
+ m->removeAccount(m->income());
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ // try to remove the account still attached to the institution
+ try {
+ m->removeAccount(a);
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ // now really remove an account
+
+ try {
+ CPPUNIT_ASSERT(i.accountCount() == 0);
+ CPPUNIT_ASSERT(m->accountCount() == 7);
+
+ a.setInstitutionId(QString());
+ m->modifyAccount(a);
+ m->removeAccount(a);
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->accountCount() == 6);
+ CPPUNIT_ASSERT(m->dirty() == true);
+ i = m->institution("I000001");
+ CPPUNIT_ASSERT(i.accountCount() == 0);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testRemoveUsedAccount() {
+ testAddTransactions();
+
+ MyMoneyAccount a = m->account("A000006");
+
+ try {
+ m->removeAccount(a);
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testRemoveInstitution() {
+ testModifyInstitution();
+ testReparentAccount();
+
+ MyMoneyInstitution i;
+ MyMoneyAccount a;
+
+ // assign the checkings account to the institution
+ try {
+ i = m->institution("I000001");
+ a = m->account("A000006");
+ a.setInstitutionId(i.id());
+ m->modifyAccount(a);
+ CPPUNIT_ASSERT(i.accountCount() == 0);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ m->m_dirty = false;
+ // now remove the institution and see if the account survived ;-)
+ try {
+ m->removeInstitution(i);
+ a.setInstitutionId(QString());
+ m->modifyAccount(a);
+ m->commitTransaction();
+ m->startTransaction();
+ a = m->account("A000006");
+ CPPUNIT_ASSERT(m->dirty() == true);
+ CPPUNIT_ASSERT(a.institutionId().isEmpty());
+ CPPUNIT_ASSERT(m->institutionCount() == 0);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testRemoveTransaction() {
+ testAddTransactions();
+
+ MyMoneyTransaction t = m->transaction("T000000000000000002");
+
+ m->m_dirty = false;
+ try {
+ m->removeTransaction(t);
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->dirty() == true);
+ CPPUNIT_ASSERT(m->transactionCount() == 1);
+/* removed with MyMoneyAccount::Transaction
+ CPPUNIT_ASSERT(m->account("A000006").transactionCount() == 1);
+*/
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testTransactionList() {
+ testAddTransactions();
+
+ QValueList<MyMoneyTransaction> list;
+ MyMoneyTransactionFilter filter("A000006");
+ list = m->transactionList(filter);
+ CPPUNIT_ASSERT(list.count() == 2);
+ CPPUNIT_ASSERT((*(list.at(0))).id() == "T000000000000000002");
+ CPPUNIT_ASSERT((*(list.at(1))).id() == "T000000000000000001");
+
+ filter.clear();
+ filter.addAccount(QString("A000003"));
+ list = m->transactionList(filter);
+ CPPUNIT_ASSERT(list.count() == 1);
+ CPPUNIT_ASSERT((*(list.at(0))).id() == "T000000000000000002");
+
+ filter.clear();
+ list = m->transactionList(filter);
+ CPPUNIT_ASSERT(list.count() == 2);
+ CPPUNIT_ASSERT((*(list.at(0))).id() == "T000000000000000002");
+ CPPUNIT_ASSERT((*(list.at(1))).id() == "T000000000000000001");
+}
+
+void MyMoneySeqAccessMgrTest::testAddPayee() {
+ MyMoneyPayee p;
+
+ p.setName("THB");
+ m->m_dirty = false;
+ try {
+ CPPUNIT_ASSERT(m->m_nextPayeeID == 0);
+ m->addPayee(p);
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->dirty() == true);
+ CPPUNIT_ASSERT(m->m_nextPayeeID == 1);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+}
+
+void MyMoneySeqAccessMgrTest::testSetAccountName() {
+ try {
+ m->setAccountName(STD_ACC_LIABILITY, "Verbindlichkeiten");
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+ try {
+ m->setAccountName(STD_ACC_ASSET, "Verm�gen");
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+ try {
+ m->setAccountName(STD_ACC_EXPENSE, "Ausgaben");
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+ try {
+ m->setAccountName(STD_ACC_INCOME, "Einnahmen");
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ CPPUNIT_ASSERT(m->liability().name() == "Verbindlichkeiten");
+ CPPUNIT_ASSERT(m->asset().name() == "Verm�gen");
+ CPPUNIT_ASSERT(m->expense().name() == "Ausgaben");
+ CPPUNIT_ASSERT(m->income().name() == "Einnahmen");
+
+ try {
+ m->setAccountName("A000001", "New account name");
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testModifyPayee() {
+ MyMoneyPayee p;
+
+ testAddPayee();
+
+ p = m->payee("P000001");
+ p.setName("New name");
+ m->m_dirty = false;
+ try {
+ m->modifyPayee(p);
+ m->commitTransaction();
+ m->startTransaction();
+ p = m->payee("P000001");
+ CPPUNIT_ASSERT(p.name() == "New name");
+ CPPUNIT_ASSERT(m->dirty() == true);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testRemovePayee() {
+ testAddPayee();
+ m->m_dirty = false;
+
+ // check that we can remove an unreferenced payee
+ MyMoneyPayee p = m->payee("P000001");
+ try {
+ CPPUNIT_ASSERT(m->m_payeeList.count() == 1);
+ m->removePayee(p);
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->m_payeeList.count() == 0);
+ CPPUNIT_ASSERT(m->dirty() == true);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ // add transaction
+ testAddTransactions();
+
+ MyMoneyTransaction tr = m->transaction("T000000000000000001");
+ MyMoneySplit sp;
+ sp = tr.splits()[0];
+ sp.setPayeeId("P000001");
+ tr.modifySplit(sp);
+
+ // check that we cannot add a transaction referencing
+ // an unknown payee
+ try {
+ m->modifyTransaction(tr);
+ CPPUNIT_FAIL("Expected exception");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+
+ m->m_nextPayeeID = 0; // reset here, so that the
+ // testAddPayee will not fail
+ testAddPayee();
+
+ // check that it works when the payee exists
+ try {
+ m->modifyTransaction(tr);
+ } catch (MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ m->m_dirty = false;
+
+ // now check, that we cannot remove the payee
+ try {
+ m->removePayee(p);
+ CPPUNIT_FAIL("Expected exception");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+ CPPUNIT_ASSERT(m->m_payeeList.count() == 1);
+}
+
+
+void MyMoneySeqAccessMgrTest::testRemoveAccountFromTree() {
+ MyMoneyAccount a, b, c;
+ a.setName("Acc A");
+ b.setName("Acc B");
+ c.setName("Acc C");
+
+ // build a tree A -> B -> C, remove B and see if A -> C
+ // remains in the storag manager
+
+ try {
+ m->addAccount(a);
+ m->addAccount(b);
+ m->addAccount(c);
+ m->reparentAccount(b, a);
+ m->reparentAccount(c, b);
+
+ CPPUNIT_ASSERT(a.accountList().count() == 1);
+ CPPUNIT_ASSERT(m->account(a.accountList()[0]).name() == "Acc B");
+
+ CPPUNIT_ASSERT(b.accountList().count() == 1);
+ CPPUNIT_ASSERT(m->account(b.accountList()[0]).name() == "Acc C");
+
+ CPPUNIT_ASSERT(c.accountList().count() == 0);
+
+ m->removeAccount(b);
+
+ // reload account info from titutionIDtorage
+ a = m->account(a.id());
+ c = m->account(c.id());
+
+ try {
+ b = m->account(b.id());
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+ CPPUNIT_ASSERT(a.accountList().count() == 1);
+ CPPUNIT_ASSERT(m->account(a.accountList()[0]).name() == "Acc C");
+
+ CPPUNIT_ASSERT(c.accountList().count() == 0);
+
+ } catch (MyMoneyException *e) {
+ unexpectedException(e);
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testPayeeName() {
+ testAddPayee();
+
+ MyMoneyPayee p;
+ QString name("THB");
+
+ // OK case
+ try {
+ p = m->payeeByName(name);
+ CPPUNIT_ASSERT(p.name() == "THB");
+ CPPUNIT_ASSERT(p.id() == "P000001");
+ } catch (MyMoneyException *e) {
+ unexpectedException(e);
+ }
+
+ // Not OK case
+ name = "Thb";
+ try {
+ p = m->payeeByName(name);
+ CPPUNIT_FAIL("Exception expected");
+ } catch (MyMoneyException *e) {
+ delete e;
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testAssignment() {
+ testAddTransactions();
+
+ MyMoneyPayee user;
+ user.setName("Thomas");
+ m->setUser(user);
+
+ MyMoneySeqAccessMgr test = *m;
+ testEquality(&test);
+}
+
+void MyMoneySeqAccessMgrTest::testEquality(const MyMoneySeqAccessMgr *t)
+{
+ CPPUNIT_ASSERT(m->user().name() == t->user().name());
+ CPPUNIT_ASSERT(m->user().address() == t->user().address());
+ CPPUNIT_ASSERT(m->user().city() == t->user().city());
+ CPPUNIT_ASSERT(m->user().state() == t->user().state());
+ CPPUNIT_ASSERT(m->user().postcode() == t->user().postcode());
+ CPPUNIT_ASSERT(m->user().telephone() == t->user().telephone());
+ CPPUNIT_ASSERT(m->user().email() == t->user().email());
+ CPPUNIT_ASSERT(m->m_nextInstitutionID == t->m_nextInstitutionID);
+ CPPUNIT_ASSERT(m->m_nextAccountID == t->m_nextAccountID);
+ CPPUNIT_ASSERT(m->m_nextTransactionID == t->m_nextTransactionID);
+ CPPUNIT_ASSERT(m->m_nextPayeeID == t->m_nextPayeeID);
+ CPPUNIT_ASSERT(m->m_nextScheduleID == t->m_nextScheduleID);
+ CPPUNIT_ASSERT(m->m_dirty == t->m_dirty);
+ CPPUNIT_ASSERT(m->m_creationDate == t->m_creationDate);
+ CPPUNIT_ASSERT(m->m_lastModificationDate == t->m_lastModificationDate);
+
+ /*
+ * make sure, that the keys and values are the same
+ * on the left and the right side
+ */
+ CPPUNIT_ASSERT(m->m_payeeList.keys() == t->m_payeeList.keys());
+ CPPUNIT_ASSERT(m->m_payeeList.values() == t->m_payeeList.values());
+ CPPUNIT_ASSERT(m->m_transactionKeys.keys() == t->m_transactionKeys.keys());
+ CPPUNIT_ASSERT(m->m_transactionKeys.values() == t->m_transactionKeys.values());
+ CPPUNIT_ASSERT(m->m_institutionList.keys() == t->m_institutionList.keys());
+ CPPUNIT_ASSERT(m->m_institutionList.values() == t->m_institutionList.values());
+ CPPUNIT_ASSERT(m->m_accountList.keys() == t->m_accountList.keys());
+ CPPUNIT_ASSERT(m->m_accountList.values() == t->m_accountList.values());
+ CPPUNIT_ASSERT(m->m_transactionList.keys() == t->m_transactionList.keys());
+ CPPUNIT_ASSERT(m->m_transactionList.values() == t->m_transactionList.values());
+ CPPUNIT_ASSERT(m->m_balanceCache.keys() == t->m_balanceCache.keys());
+ CPPUNIT_ASSERT(m->m_balanceCache.values() == t->m_balanceCache.values());
+
+// CPPUNIT_ASSERT(m->m_scheduleList.keys() == t->m_scheduleList.keys());
+// CPPUNIT_ASSERT(m->m_scheduleList.values() == t->m_scheduleList.values());
+}
+
+void MyMoneySeqAccessMgrTest::testDuplicate() {
+ const MyMoneySeqAccessMgr* t;
+
+ testModifyTransaction();
+
+ t = m->duplicate();
+ testEquality(t);
+ delete t;
+}
+
+void MyMoneySeqAccessMgrTest::testAddSchedule() {
+ /* Note addSchedule() now calls validate as it should
+ * so we need an account id. Later this will
+ * be checked to make sure its a valid account id. The
+ * tests currently fail because no splits are defined
+ * for the schedules transaction.
+ */
+
+
+ try {
+ CPPUNIT_ASSERT(m->m_scheduleList.count() == 0);
+ MyMoneyTransaction t1;
+ MyMoneySplit s1, s2;
+ s1.setAccountId("A000001");
+ t1.addSplit(s1);
+ s2.setAccountId("A000002");
+ t1.addSplit(s2);
+ MyMoneySchedule schedule("Sched-Name",
+ MyMoneySchedule::TYPE_DEPOSIT,
+ MyMoneySchedule::OCCUR_DAILY, 1,
+ MyMoneySchedule::STYPE_MANUALDEPOSIT,
+ QDate(),
+ QDate(),
+ true,
+ false);
+ t1.setPostDate(QDate(2003,7,10));
+ schedule.setTransaction(t1);
+
+ m->addSchedule(schedule);
+
+ CPPUNIT_ASSERT(m->m_scheduleList.count() == 1);
+ CPPUNIT_ASSERT(schedule.id() == "SCH000001");
+ CPPUNIT_ASSERT(m->m_scheduleList["SCH000001"].id() == "SCH000001");
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ try {
+ MyMoneySchedule schedule("Sched-Name",
+ MyMoneySchedule::TYPE_DEPOSIT,
+ MyMoneySchedule::OCCUR_DAILY, 1,
+ MyMoneySchedule::STYPE_MANUALDEPOSIT,
+ QDate(),
+ QDate(),
+ true,
+ false);
+ m->addSchedule(schedule);
+ CPPUNIT_FAIL("Exception expected");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testSchedule() {
+ testAddSchedule();
+ MyMoneySchedule sched;
+
+ sched = m->schedule("SCH000001");
+ CPPUNIT_ASSERT(sched.name() == "Sched-Name");
+ CPPUNIT_ASSERT(sched.id() == "SCH000001");
+
+ try {
+ m->schedule("SCH000002");
+ CPPUNIT_FAIL("Exception expected");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testModifySchedule() {
+ testAddSchedule();
+ MyMoneySchedule sched;
+
+ sched = m->schedule("SCH000001");
+ sched.setId("SCH000002");
+ try {
+ m->modifySchedule(sched);
+ CPPUNIT_FAIL("Exception expected");
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+
+ sched = m->schedule("SCH000001");
+ sched.setName("New Sched-Name");
+ try {
+ m->modifySchedule(sched);
+ CPPUNIT_ASSERT(m->m_scheduleList.count() == 1);
+ CPPUNIT_ASSERT(m->m_scheduleList["SCH000001"].name() == "New Sched-Name");
+
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+}
+
+void MyMoneySeqAccessMgrTest::testRemoveSchedule() {
+ testAddSchedule();
+ m->commitTransaction();
+ m->startTransaction();
+ MyMoneySchedule sched;
+
+ sched = m->schedule("SCH000001");
+ sched.setId("SCH000002");
+ try {
+ m->removeSchedule(sched);
+ m->commitTransaction();
+ CPPUNIT_FAIL("Exception expected");
+ } catch(MyMoneyException *e) {
+ m->rollbackTransaction();
+ delete e;
+ }
+ m->startTransaction();
+
+ sched = m->schedule("SCH000001");
+ try {
+ m->removeSchedule(sched);
+ m->commitTransaction();
+ CPPUNIT_ASSERT(m->m_scheduleList.count() == 0);
+
+ } catch(MyMoneyException *e) {
+ m->rollbackTransaction();
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+ m->startTransaction();
+}
+
+void MyMoneySeqAccessMgrTest::testScheduleList() {
+ QDate testDate = QDate::currentDate();
+ QDate notOverdue = testDate.addDays(2);
+ QDate overdue = testDate.addDays(-2);
+
+
+ MyMoneyTransaction t1;
+ MyMoneySplit s1, s2;
+ s1.setAccountId("A000001");
+ t1.addSplit(s1);
+ s2.setAccountId("A000002");
+ t1.addSplit(s2);
+ MyMoneySchedule schedule1("Schedule 1",
+ MyMoneySchedule::TYPE_BILL,
+ MyMoneySchedule::OCCUR_ONCE, 1,
+ MyMoneySchedule::STYPE_DIRECTDEBIT,
+ QDate(),
+ QDate(),
+ false,
+ false);
+ t1.setPostDate(notOverdue);
+ schedule1.setTransaction(t1);
+ schedule1.setLastPayment(notOverdue);
+
+ MyMoneyTransaction t2;
+ MyMoneySplit s3, s4;
+ s3.setAccountId("A000001");
+ t2.addSplit(s3);
+ s4.setAccountId("A000003");
+ t2.addSplit(s4);
+ MyMoneySchedule schedule2("Schedule 2",
+ MyMoneySchedule::TYPE_DEPOSIT,
+ MyMoneySchedule::OCCUR_DAILY, 1,
+ MyMoneySchedule::STYPE_DIRECTDEPOSIT,
+ QDate(),
+ QDate(),
+ false,
+ false);
+ t2.setPostDate(notOverdue.addDays(1));
+ schedule2.setTransaction(t2);
+ schedule2.setLastPayment(notOverdue.addDays(1));
+
+ MyMoneyTransaction t3;
+ MyMoneySplit s5, s6;
+ s5.setAccountId("A000005");
+ t3.addSplit(s5);
+ s6.setAccountId("A000006");
+ t3.addSplit(s6);
+ MyMoneySchedule schedule3("Schedule 3",
+ MyMoneySchedule::TYPE_TRANSFER,
+ MyMoneySchedule::OCCUR_WEEKLY, 1,
+ MyMoneySchedule::STYPE_OTHER,
+ QDate(),
+ QDate(),
+ false,
+ false);
+ t3.setPostDate(notOverdue.addDays(2));
+ schedule3.setTransaction(t3);
+ schedule3.setLastPayment(notOverdue.addDays(2));
+
+ MyMoneyTransaction t4;
+ MyMoneySplit s7, s8;
+ s7.setAccountId("A000005");
+ t4.addSplit(s7);
+ s8.setAccountId("A000006");
+ t4.addSplit(s8);
+ MyMoneySchedule schedule4("Schedule 4",
+ MyMoneySchedule::TYPE_BILL,
+ MyMoneySchedule::OCCUR_WEEKLY, 1,
+ MyMoneySchedule::STYPE_WRITECHEQUE,
+ QDate(),
+ notOverdue.addDays(31),
+ false,
+ false);
+ t4.setPostDate(overdue.addDays(-7));
+ schedule4.setTransaction(t4);
+
+ try {
+ m->addSchedule(schedule1);
+ m->addSchedule(schedule2);
+ m->addSchedule(schedule3);
+ m->addSchedule(schedule4);
+ } catch(MyMoneyException *e) {
+ qDebug("Error: %s", e->what().latin1());
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ QValueList<MyMoneySchedule> list;
+
+ // no filter
+ list = m->scheduleList();
+ CPPUNIT_ASSERT(list.count() == 4);
+
+ // filter by type
+ list = m->scheduleList("", MyMoneySchedule::TYPE_BILL);
+ CPPUNIT_ASSERT(list.count() == 2);
+ CPPUNIT_ASSERT(list[0].name() == "Schedule 1");
+ CPPUNIT_ASSERT(list[1].name() == "Schedule 4");
+
+ // filter by occurence
+ list = m->scheduleList("", MyMoneySchedule::TYPE_ANY,
+ MyMoneySchedule::OCCUR_DAILY);
+ CPPUNIT_ASSERT(list.count() == 1);
+ CPPUNIT_ASSERT(list[0].name() == "Schedule 2");
+
+ // filter by payment type
+ list = m->scheduleList("", MyMoneySchedule::TYPE_ANY,
+ MyMoneySchedule::OCCUR_ANY,
+ MyMoneySchedule::STYPE_DIRECTDEPOSIT);
+ CPPUNIT_ASSERT(list.count() == 1);
+ CPPUNIT_ASSERT(list[0].name() == "Schedule 2");
+
+ // filter by account
+ list = m->scheduleList("A01");
+ CPPUNIT_ASSERT(list.count() == 0);
+ list = m->scheduleList("A000001");
+ CPPUNIT_ASSERT(list.count() == 2);
+ list = m->scheduleList("A000002");
+ CPPUNIT_ASSERT(list.count() == 1);
+
+ // filter by start date
+ list = m->scheduleList("", MyMoneySchedule::TYPE_ANY,
+ MyMoneySchedule::OCCUR_ANY,
+ MyMoneySchedule::STYPE_ANY,
+ notOverdue.addDays(31));
+ CPPUNIT_ASSERT(list.count() == 3);
+ CPPUNIT_ASSERT(list[0].name() == "Schedule 2");
+ CPPUNIT_ASSERT(list[1].name() == "Schedule 3");
+ CPPUNIT_ASSERT(list[2].name() == "Schedule 4");
+
+ // filter by end date
+ list = m->scheduleList("", MyMoneySchedule::TYPE_ANY,
+ MyMoneySchedule::OCCUR_ANY,
+ MyMoneySchedule::STYPE_ANY,
+ QDate(),
+ notOverdue.addDays(1));
+ CPPUNIT_ASSERT(list.count() == 3);
+ CPPUNIT_ASSERT(list[0].name() == "Schedule 1");
+ CPPUNIT_ASSERT(list[1].name() == "Schedule 2");
+ CPPUNIT_ASSERT(list[2].name() == "Schedule 4");
+
+ // filter by start and end date
+ list = m->scheduleList("", MyMoneySchedule::TYPE_ANY,
+ MyMoneySchedule::OCCUR_ANY,
+ MyMoneySchedule::STYPE_ANY,
+ notOverdue.addDays(-1),
+ notOverdue.addDays(1));
+ CPPUNIT_ASSERT(list.count() == 2);
+ CPPUNIT_ASSERT(list[0].name() == "Schedule 1");
+ CPPUNIT_ASSERT(list[1].name() == "Schedule 2");
+
+ // filter by overdue status
+ list = m->scheduleList("", MyMoneySchedule::TYPE_ANY,
+ MyMoneySchedule::OCCUR_ANY,
+ MyMoneySchedule::STYPE_ANY,
+ QDate(),
+ QDate(),
+ true);
+ CPPUNIT_ASSERT(list.count() == 1);
+ CPPUNIT_ASSERT(list[0].name() == "Schedule 4");
+}
+
+void MyMoneySeqAccessMgrTest::testAddCurrency()
+{
+ MyMoneySecurity curr("EUR", "Euro", "?", 100, 100);
+ CPPUNIT_ASSERT(m->m_currencyList.count() == 0);
+ m->m_dirty = false;
+ try {
+ m->addCurrency(curr);
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->m_currencyList.count() == 1);
+ CPPUNIT_ASSERT(m->m_currencyList["EUR"].name() == "Euro");
+ CPPUNIT_ASSERT(m->dirty() == true);
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ m->m_dirty = false;
+ try {
+ m->addCurrency(curr);
+ CPPUNIT_FAIL("Expected exception missing");
+ } catch(MyMoneyException *e) {
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->dirty() == false);
+ delete e;
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testModifyCurrency()
+{
+ MyMoneySecurity curr("EUR", "Euro", "?", 100, 100);
+ testAddCurrency();
+ m->m_dirty = false;
+ curr.setName("EURO");
+ try {
+ m->modifyCurrency(curr);
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->m_currencyList.count() == 1);
+ CPPUNIT_ASSERT(m->m_currencyList["EUR"].name() == "EURO");
+ CPPUNIT_ASSERT(m->dirty() == true);
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ m->m_dirty = false;
+
+ MyMoneySecurity unknownCurr("DEM", "Deutsche Mark", "DM", 100, 100);
+ try {
+ m->modifyCurrency(unknownCurr);
+ CPPUNIT_FAIL("Expected exception missing");
+ } catch(MyMoneyException *e) {
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->dirty() == false);
+ delete e;
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testRemoveCurrency()
+{
+ MyMoneySecurity curr("EUR", "Euro", "?", 100, 100);
+ testAddCurrency();
+ m->m_dirty = false;
+ try {
+ m->removeCurrency(curr);
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->m_currencyList.count() == 0);
+ CPPUNIT_ASSERT(m->dirty() == true);
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ m->m_dirty = false;
+
+ MyMoneySecurity unknownCurr("DEM", "Deutsche Mark", "DM", 100, 100);
+ try {
+ m->removeCurrency(unknownCurr);
+ CPPUNIT_FAIL("Expected exception missing");
+ } catch(MyMoneyException *e) {
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->dirty() == false);
+ delete e;
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testCurrency()
+{
+ MyMoneySecurity curr("EUR", "Euro", "?", 100, 100);
+ MyMoneySecurity newCurr;
+ testAddCurrency();
+ m->m_dirty = false;
+ try {
+ newCurr = m->currency("EUR");
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->dirty() == false);
+ CPPUNIT_ASSERT(newCurr.id() == curr.id());
+ CPPUNIT_ASSERT(newCurr.name() == curr.name());
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+
+ try {
+ m->currency("DEM");
+ CPPUNIT_FAIL("Expected exception missing");
+ } catch(MyMoneyException *e) {
+ m->commitTransaction();
+ m->startTransaction();
+ CPPUNIT_ASSERT(m->dirty() == false);
+ delete e;
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testCurrencyList()
+{
+ CPPUNIT_ASSERT(m->currencyList().count() == 0);
+
+ testAddCurrency();
+ CPPUNIT_ASSERT(m->currencyList().count() == 1);
+
+ MyMoneySecurity unknownCurr("DEM", "Deutsche Mark", "DM", 100, 100);
+ try {
+ m->addCurrency(unknownCurr);
+ m->m_dirty = false;
+ CPPUNIT_ASSERT(m->m_currencyList.count() == 2);
+ CPPUNIT_ASSERT(m->currencyList().count() == 2);
+ CPPUNIT_ASSERT(m->dirty() == false);
+ } catch(MyMoneyException *e) {
+ delete e;
+ CPPUNIT_FAIL("Unexpected exception");
+ }
+}
+
+void MyMoneySeqAccessMgrTest::testAccountList()
+{
+ QValueList<MyMoneyAccount> accounts;
+ m->accountList(accounts);
+ CPPUNIT_ASSERT(accounts.count() == 0);
+ testAddNewAccount();
+ accounts.clear();
+ m->accountList(accounts);
+ CPPUNIT_ASSERT(accounts.count() == 2);
+
+ MyMoneyAccount a = m->account("A000001");
+ MyMoneyAccount b = m->account("A000002");
+ m->reparentAccount(b, a);
+ accounts.clear();
+ m->accountList(accounts);
+ CPPUNIT_ASSERT(accounts.count() == 2);
+}
+
+void MyMoneySeqAccessMgrTest::testLoaderFunctions()
+{
+ // we don't need the transaction started by setup() here
+ m->rollbackTransaction();
+
+ // account loader
+ QMap<QString, MyMoneyAccount> amap;
+ MyMoneyAccount acc("A0000176", MyMoneyAccount());
+ amap[acc.id()] = acc;
+ m->loadAccounts(amap);
+ CPPUNIT_ASSERT(m->m_accountList.values() == amap.values());
+ CPPUNIT_ASSERT(m->m_accountList.keys() == amap.keys());
+ CPPUNIT_ASSERT(m->m_nextAccountID == 176);
+
+ // transaction loader
+ QMap<QString, MyMoneyTransaction> tmap;
+ MyMoneyTransaction t("T000000108", MyMoneyTransaction());
+ tmap[t.id()] = t;
+ m->loadTransactions(tmap);
+ CPPUNIT_ASSERT(m->m_transactionList.values() == tmap.values());
+ CPPUNIT_ASSERT(m->m_transactionList.keys() == tmap.keys());
+ CPPUNIT_ASSERT(m->m_nextTransactionID == 108);
+
+ // institution loader
+ QMap<QString, MyMoneyInstitution> imap;
+ MyMoneyInstitution inst("I000028", MyMoneyInstitution());
+ imap[inst.id()] = inst;
+ m->loadInstitutions(imap);
+ CPPUNIT_ASSERT(m->m_institutionList.values() == imap.values());
+ CPPUNIT_ASSERT(m->m_institutionList.keys() == imap.keys());
+ CPPUNIT_ASSERT(m->m_nextInstitutionID == 28);
+
+ // payee loader
+ QMap<QString, MyMoneyPayee> pmap;
+ MyMoneyPayee p("P1234", MyMoneyPayee());
+ pmap[p.id()] = p;
+ m->loadPayees(pmap);
+ CPPUNIT_ASSERT(m->m_payeeList.values() == pmap.values());
+ CPPUNIT_ASSERT(m->m_payeeList.keys() == pmap.keys());
+ CPPUNIT_ASSERT(m->m_nextPayeeID == 1234);
+
+ // security loader
+ QMap<QString, MyMoneySecurity> smap;
+ MyMoneySecurity s("S54321", MyMoneySecurity());
+ smap[s.id()] = s;
+ m->loadSecurities(smap);
+ CPPUNIT_ASSERT(m->m_securitiesList.values() == smap.values());
+ CPPUNIT_ASSERT(m->m_securitiesList.keys() == smap.keys());
+ CPPUNIT_ASSERT(m->m_nextSecurityID == 54321);
+
+ // schedule loader
+ QMap<QString, MyMoneySchedule> schmap;
+ MyMoneySchedule sch("SCH6789", MyMoneySchedule());
+ schmap[sch.id()] = sch;
+ m->loadSchedules(schmap);
+ CPPUNIT_ASSERT(m->m_scheduleList.values() == schmap.values());
+ CPPUNIT_ASSERT(m->m_scheduleList.keys() == schmap.keys());
+ CPPUNIT_ASSERT(m->m_nextScheduleID == 6789);
+
+ // report loader
+ QMap<QString, MyMoneyReport> rmap;
+ MyMoneyReport r("R1298", MyMoneyReport());
+ rmap[r.id()] = r;
+ m->loadReports(rmap);
+ CPPUNIT_ASSERT(m->m_reportList.values() == rmap.values());
+ CPPUNIT_ASSERT(m->m_reportList.keys() == rmap.keys());
+ CPPUNIT_ASSERT(m->m_nextReportID == 1298);
+
+ // budget loader
+ QMap<QString, MyMoneyBudget> bmap;
+ MyMoneyBudget b("B89765", MyMoneyBudget());
+ bmap[b.id()] = b;
+ m->loadBudgets(bmap);
+ CPPUNIT_ASSERT(m->m_budgetList.values() == bmap.values());
+ CPPUNIT_ASSERT(m->m_budgetList.keys() == bmap.keys());
+ CPPUNIT_ASSERT(m->m_nextBudgetID == 89765);
+
+ // restart a transaction so that teardown() is happy
+ m->startTransaction();
+}
+
diff --git a/kmymoney2/mymoney/storage/mymoneyseqaccessmgrtest.h b/kmymoney2/mymoney/storage/mymoneyseqaccessmgrtest.h
new file mode 100644
index 0000000..b9fa763
--- /dev/null
+++ b/kmymoney2/mymoney/storage/mymoneyseqaccessmgrtest.h
@@ -0,0 +1,131 @@
+/***************************************************************************
+ mymoneyseqaccessmgrtest.h
+ -------------------
+ copyright : (C) 2002 by Thomas Baumgart
+ email : ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 __MYMONEYSEQACCESSMGRTEST_H__
+#define __MYMONEYSEQACCESSMGRTEST_H__
+
+#include <cppunit/TestCaller.h>
+#include <cppunit/TestCase.h>
+#include <cppunit/TestSuite.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include "../autotest.h"
+
+#define private public
+#define protected public
+#include "../mymoneyobject.h"
+#include "mymoneyseqaccessmgr.h"
+#undef private
+
+class MyMoneySeqAccessMgrTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(MyMoneySeqAccessMgrTest);
+ CPPUNIT_TEST(testEmptyConstructor);
+ CPPUNIT_TEST(testSetFunctions);
+ CPPUNIT_TEST(testSupportFunctions);
+ CPPUNIT_TEST(testIsStandardAccount);
+ CPPUNIT_TEST(testNewAccount);
+ CPPUNIT_TEST(testAddNewAccount);
+ CPPUNIT_TEST(testReparentAccount);
+ CPPUNIT_TEST(testAddInstitution);
+ CPPUNIT_TEST(testInstitution);
+ CPPUNIT_TEST(testAccount2Institution);
+ CPPUNIT_TEST(testModifyAccount);
+ CPPUNIT_TEST(testModifyInstitution);
+ CPPUNIT_TEST(testAddTransactions);
+ CPPUNIT_TEST(testTransactionCount);
+ CPPUNIT_TEST(testBalance);
+ CPPUNIT_TEST(testModifyTransaction);
+ CPPUNIT_TEST(testRemoveUnusedAccount);
+ CPPUNIT_TEST(testRemoveUsedAccount);
+ CPPUNIT_TEST(testRemoveInstitution);
+ CPPUNIT_TEST(testRemoveTransaction);
+ CPPUNIT_TEST(testTransactionList);
+ CPPUNIT_TEST(testAddPayee);
+ CPPUNIT_TEST(testSetAccountName);
+ CPPUNIT_TEST(testModifyPayee);
+ CPPUNIT_TEST(testPayeeName);
+ CPPUNIT_TEST(testRemovePayee);
+ CPPUNIT_TEST(testRemoveAccountFromTree);
+ CPPUNIT_TEST(testAssignment);
+ CPPUNIT_TEST(testDuplicate);
+ CPPUNIT_TEST(testAddSchedule);
+ CPPUNIT_TEST(testModifySchedule);
+ CPPUNIT_TEST(testRemoveSchedule);
+ CPPUNIT_TEST(testSchedule);
+ CPPUNIT_TEST(testScheduleList);
+ CPPUNIT_TEST(testAddCurrency);
+ CPPUNIT_TEST(testModifyCurrency);
+ CPPUNIT_TEST(testRemoveCurrency);
+ CPPUNIT_TEST(testCurrency);
+ CPPUNIT_TEST(testCurrencyList);
+ CPPUNIT_TEST(testAccountList);
+ CPPUNIT_TEST(testLoaderFunctions);
+ CPPUNIT_TEST_SUITE_END();
+
+protected:
+ MyMoneySeqAccessMgr *m;
+public:
+ MyMoneySeqAccessMgrTest();
+
+
+ void setUp();
+ void tearDown();
+ void testEmptyConstructor();
+ void testSetFunctions();
+ void testIsStandardAccount();
+ void testNewAccount();
+ void testAccount();
+ void testAddNewAccount();
+ void testAddInstitution();
+ void testInstitution();
+ void testAccount2Institution();
+ void testModifyAccount();
+ void testModifyInstitution();
+ void testReparentAccount();
+ void testAddTransactions();
+ void testTransactionCount();
+ void testBalance();
+ void testModifyTransaction();
+ void testRemoveUnusedAccount();
+ void testRemoveUsedAccount();
+ void testRemoveInstitution();
+ void testRemoveTransaction();
+ void testTransactionList();
+ void testAddPayee();
+ void testSetAccountName();
+ void testModifyPayee();
+ void testPayeeName();
+ void testRemovePayee();
+ void testRemoveAccountFromTree();
+ void testAssignment();
+ void testEquality(const MyMoneySeqAccessMgr* t);
+ void testDuplicate();
+ void testAddSchedule();
+ void testSchedule();
+ void testModifySchedule();
+ void testRemoveSchedule();
+ void testSupportFunctions();
+ void testScheduleList();
+ void testAddCurrency();
+ void testModifyCurrency();
+ void testRemoveCurrency();
+ void testCurrency();
+ void testCurrencyList();
+ void testAccountList();
+ void testLoaderFunctions();
+};
+
+#endif
diff --git a/kmymoney2/mymoney/storage/mymoneystorageanon.cpp b/kmymoney2/mymoney/storage/mymoneystorageanon.cpp
new file mode 100644
index 0000000..31f051e
--- /dev/null
+++ b/kmymoney2/mymoney/storage/mymoneystorageanon.cpp
@@ -0,0 +1,294 @@
+/***************************************************************************
+ mymoneystorageanon.cpp
+ -------------------
+ begin : Thu Oct 24 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ Ace Jones <acejones@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "config.h"
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qfile.h>
+#include <qdom.h>
+#include <qmap.h>
+
+// ----------------------------------------------------------------------------
+// KDE Includes
+
+#include "kdecompat.h"
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "mymoneystorageanon.h"
+#include "../mymoneyreport.h"
+#include "../mymoneyinstitution.h"
+
+QStringList MyMoneyStorageANON::zKvpNoModify = QStringList::split(",","kmm-baseCurrency,PreferredAccount,Tax,fixed-interest,interest-calculation,payee,schedule,term,kmm-online-source,kmm-brokerage-account,lastStatementDate,kmm-sort-reconcile,kmm-sort-std,kmm-iconpos,mm-closed,payee,schedule,term,lastImportedTransactionDate,VatAccount,VatRate,kmm-matched-tx,Imported");
+QStringList MyMoneyStorageANON::zKvpXNumber = QStringList::split(",","final-payment,loan-amount,periodic-payment,lastStatementBalance");
+
+
+MyMoneyStorageANON::MyMoneyStorageANON() :
+ MyMoneyStorageXML()
+{
+ // Choose a quasi-random 0.0-100.0 factor which will be applied to all splits this time
+ // around.
+
+ int msec;
+ do {
+ msec = QTime::currentTime().msec();
+ } while(msec == 0);
+ m_factor = MyMoneyMoney(msec, 10).reduce();
+}
+
+MyMoneyStorageANON::~MyMoneyStorageANON()
+{
+}
+
+void MyMoneyStorageANON::readFile(QIODevice* , IMyMoneySerialize* )
+{
+ throw new MYMONEYEXCEPTION("Cannot read a file through MyMoneyStorageANON!!");
+}
+
+void MyMoneyStorageANON::writeUserInformation(QDomElement& userInfo)
+{
+ MyMoneyPayee user = m_storage->user();
+
+ userInfo.setAttribute(QString("name"), hideString(user.name()));
+ userInfo.setAttribute(QString("email"), hideString(user.email()));
+
+ QDomElement address = m_doc->createElement("ADDRESS");
+ address.setAttribute(QString("street"), hideString(user.address()));
+ address.setAttribute(QString("city"), hideString(user.city()));
+ address.setAttribute(QString("county"), hideString(user.state()));
+ address.setAttribute(QString("zipcode"), hideString(user.postcode()));
+ address.setAttribute(QString("telephone"), hideString(user.telephone()));
+
+ userInfo.appendChild(address);
+}
+
+void MyMoneyStorageANON::writeInstitution(QDomElement& institution, const MyMoneyInstitution& _i)
+{
+ MyMoneyInstitution i(_i);
+
+ // mangle fields
+ i.setName(i.id());
+ i.setManager(hideString(i.manager()));
+ i.setSortcode(hideString(i.sortcode()));
+
+ i.setStreet(hideString(i.street()));
+ i.setCity(hideString(i.city()));
+ i.setPostcode(hideString(i.postcode()));
+ i.setTelephone(hideString(i.telephone()));
+
+ MyMoneyStorageXML::writeInstitution(institution, i);
+}
+
+
+void MyMoneyStorageANON::writePayee(QDomElement& payee, const MyMoneyPayee& _p)
+{
+ MyMoneyPayee p(_p);
+
+ p.setName(p.id());
+ p.setReference(hideString(p.reference()));
+
+ p.setAddress(hideString(p.address()));
+ p.setCity(hideString(p.city()));
+ p.setPostcode(hideString(p.postcode()));
+ p.setState(hideString(p.state()));
+ p.setTelephone(hideString(p.telephone()));
+ p.setNotes(hideString(p.notes()));
+ bool ignoreCase;
+ QStringList keys;
+ MyMoneyPayee::payeeMatchType matchType = p.matchData(ignoreCase, keys);
+ QRegExp exp("[A-Za-z]");
+ p.setMatchData(matchType, ignoreCase, QStringList::split(";", keys.join(";").replace(exp, "x")));
+
+ MyMoneyStorageXML::writePayee(payee, p);
+}
+
+void MyMoneyStorageANON::writeAccount(QDomElement& account, const MyMoneyAccount& _p)
+{
+ MyMoneyAccount p(_p);
+
+ p.setNumber(hideString(p.number()));
+ p.setName(p.id());
+ p.setDescription(hideString(p.description()));
+ fakeKeyValuePair(p);
+
+ // Remove the online banking settings entirely.
+ p.setOnlineBankingSettings(MyMoneyKeyValueContainer());
+
+ MyMoneyStorageXML::writeAccount(account, p);
+}
+
+void MyMoneyStorageANON::fakeTransaction(MyMoneyTransaction& tx)
+{
+ MyMoneyTransaction tn = tx;
+
+ // hide transaction data
+ tn.setMemo(tx.id());
+ tn.setBankID(hideString(tx.bankID()));
+
+ // hide split data
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+ for(it_s = tx.splits().begin(); it_s != tx.splits().end(); ++it_s) {
+ MyMoneySplit s = (*it_s);
+ s.setMemo(QString("%1/%2").arg(tn.id()).arg(s.id()));
+
+ if(s.value() != MyMoneyMoney::autoCalc) {
+ s.setValue((s.value() * m_factor));
+ s.setShares((s.shares() * m_factor));
+ }
+ s.setNumber(hideString(s.number()));
+
+ // obfuscate a possibly matched transaction as well
+ if(s.isMatched()) {
+ MyMoneyTransaction t = s.matchedTransaction();
+ fakeTransaction(t);
+ s.removeMatch();
+ s.addMatch(t);
+ }
+ tn.modifySplit(s);
+ }
+ tx = tn;
+ fakeKeyValuePair(tx);
+}
+
+void MyMoneyStorageANON::fakeKeyValuePair(MyMoneyKeyValueContainer& kvp)
+{
+ QMap<QString, QString> pairs;
+ QMap<QString, QString>::const_iterator it;
+
+ for(it = kvp.pairs().begin(); it != kvp.pairs().end(); ++it)
+ {
+ if ( zKvpXNumber.contains( it.key() ) || it.key().left(3)=="ir-" )
+ pairs[it.key()] = hideNumber(MyMoneyMoney(it.data())).toString();
+ else if ( zKvpNoModify.contains( it.key() ) )
+ pairs[it.key()] = it.data();
+ else
+ pairs[it.key()] = hideString(it.data());
+ }
+ kvp.setPairs(pairs);
+}
+
+void MyMoneyStorageANON::writeTransaction(QDomElement& transactions, const MyMoneyTransaction& tx)
+{
+ MyMoneyTransaction tn = tx;
+
+ fakeTransaction(tn);
+
+ MyMoneyStorageXML::writeTransaction(transactions, tn);
+}
+
+void MyMoneyStorageANON::writeSchedule(QDomElement& scheduledTx, const MyMoneySchedule& sx)
+{
+ MyMoneySchedule sn = sx;
+ MyMoneyTransaction tn = sn.transaction();
+
+ fakeTransaction(tn);
+
+ sn.setName(sx.id());
+ sn.setTransaction(tn, true);
+
+ MyMoneyStorageXML::writeSchedule(scheduledTx, sn);
+}
+
+void MyMoneyStorageANON::writeSecurity(QDomElement& securityElement, const MyMoneySecurity& security)
+{
+ MyMoneySecurity s = security;
+ s.setName(security.id());
+ fakeKeyValuePair(s);
+
+ MyMoneyStorageXML::writeSecurity(securityElement, s);
+}
+
+QString MyMoneyStorageANON::hideString(const QString& _in) const
+{
+ return QString(_in).fill('x');
+}
+
+MyMoneyMoney MyMoneyStorageANON::hideNumber(const MyMoneyMoney& _in) const
+{
+ MyMoneyMoney result;
+ static MyMoneyMoney counter = MyMoneyMoney(100,100);
+
+ // preserve sign
+ if ( _in.isNegative() )
+ result = MyMoneyMoney(-1);
+ else
+ result = MyMoneyMoney(1);
+
+ result = result * counter;
+ counter += MyMoneyMoney("10/100");
+
+ // preserve > 1000
+ if ( _in >= MyMoneyMoney(1000) )
+ result = result * MyMoneyMoney(1000);
+ if ( _in <= MyMoneyMoney(-1000) )
+ result = result * MyMoneyMoney(1000);
+
+ return result.convert();
+}
+
+void MyMoneyStorageANON::fakeBudget(MyMoneyBudget& bx)
+{
+ MyMoneyBudget bn;
+
+ bn.setName(bx.name());
+ bn.setBudgetStart(bx.budgetStart());
+ bn = MyMoneyBudget(bx.id(), bn);
+
+ QValueList<MyMoneyBudget::AccountGroup> list = bx.getaccounts();
+ QValueList<MyMoneyBudget::AccountGroup>::iterator it;
+ for(it = list.begin(); it != list.end(); ++it) {
+ // only add the account if there is a budget entered
+ if(!(*it).balance().isZero()) {
+ MyMoneyBudget::AccountGroup account;
+ account.setId((*it).id());
+ account.setBudgetLevel((*it).budgetLevel());
+ account.setBudgetSubaccounts((*it).budgetSubaccounts());
+ QMap<QDate, MyMoneyBudget::PeriodGroup> plist = (*it).getPeriods();
+ QMap<QDate, MyMoneyBudget::PeriodGroup>::const_iterator it_p;
+ for(it_p = plist.begin(); it_p != plist.end(); ++it_p) {
+ MyMoneyBudget::PeriodGroup pGroup;
+ pGroup.setAmount((*it_p).amount() * m_factor );
+ pGroup.setStartDate( (*it_p).startDate());
+ account.addPeriod(pGroup.startDate(), pGroup);
+ }
+ bn.setAccount(account, account.id());
+ }
+ }
+
+ bx = bn;
+}
+
+void MyMoneyStorageANON::writeBudget(QDomElement& budgets, const MyMoneyBudget& b)
+{
+ MyMoneyBudget bn = b;
+
+ fakeBudget(bn);
+
+ MyMoneyStorageXML::writeBudget(budgets, bn);
+}
+
+
+// vim:cin:si:ai:et:ts=2:sw=2:
diff --git a/kmymoney2/mymoney/storage/mymoneystorageanon.h b/kmymoney2/mymoney/storage/mymoneystorageanon.h
new file mode 100644
index 0000000..4b7ab95
--- /dev/null
+++ b/kmymoney2/mymoney/storage/mymoneystorageanon.h
@@ -0,0 +1,113 @@
+/***************************************************************************
+ mymoneystorageanon.h
+ -------------------
+ begin : Thu Oct 24 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ Ace Jone <acejones@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYSTORAGEANON_H
+#define MYMONEYSTORAGEANON_H
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+// #include <qdom.h>
+// #include <qdatastream.h>
+// class QIODevice;
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+// #include "imymoneyserialize.h"
+// #include "imymoneystorageformat.h"
+#include "mymoneystoragexml.h"
+
+/**
+ * @author Kevin Tambascio (ktambascio@users.sourceforge.net)
+ */
+
+#define VERSION_0_60_XML 0x10000010 // Version 0.5 file version info
+#define VERSION_0_61_XML 0x10000011 // use 8 bytes for MyMoneyMoney objects
+
+/**
+ * This class provides storage of an anonymized version of the current
+ * file. Any object with an ID (account, transaction, etc) is renamed
+ * with that ID. Any other string value the user typed in is replaced with
+ * x's equal in length to the original string. Any numeric value is
+ * replaced with an arbitrary number which matches the sign of the original.
+ *
+ * The purpose of this class is to give users a way to send a developer
+ * their file without comprimising their financial data. If a user
+ * encounters an error, they should try saving the anonymous version of the
+ * file and see if the error is still there. If so, they should notify the
+ * list of the problem, and then when requested, send the anonymous file
+ * privately to the developer who takes the problem. I still don't think
+ * it's wise to post the file to the public list...maybe I'm just paranoid.
+ *
+ * @author Ace Jones <ace.j@hotpop.com>
+ */
+
+class MyMoneyStorageANON : public MyMoneyStorageXML
+{
+public:
+ MyMoneyStorageANON();
+ virtual ~MyMoneyStorageANON();
+
+protected:
+ void writeUserInformation(QDomElement& userInfo);
+
+ void writeInstitution(QDomElement& institutions, const MyMoneyInstitution& i);
+
+ void writePayee(QDomElement& payees, const MyMoneyPayee& p);
+
+ void writeAccount(QDomElement& accounts, const MyMoneyAccount& p);
+
+ void writeTransaction(QDomElement& transactions, const MyMoneyTransaction& tx);
+
+ void writeSchedule(QDomElement& scheduledTx, const MyMoneySchedule& tx);
+
+ void writeBudget(QDomElement& budgets, const MyMoneyBudget& b);
+
+ void readFile(QIODevice* s, IMyMoneySerialize* storage);
+
+ void writeSecurity(QDomElement& securityElement, const MyMoneySecurity& security);
+
+ QDomElement findChildElement(const QString& name, const QDomElement& root);
+
+private:
+ /**
+ * The list of key-value pairs to not modify
+ */
+ static QStringList zKvpNoModify;
+
+ /**
+ * The list of key-value pairs which are numbers to be hidden
+ */
+ static QStringList zKvpXNumber;
+
+ QString hideString(const QString&) const;
+ MyMoneyMoney hideNumber(const MyMoneyMoney&) const;
+ void fakeTransaction(MyMoneyTransaction& tn);
+ void fakeBudget(MyMoneyBudget& bn);
+ void fakeKeyValuePair(MyMoneyKeyValueContainer& _kvp);
+
+ MyMoneyMoney m_factor;
+};
+
+#endif
diff --git a/kmymoney2/mymoney/storage/mymoneystoragebin.h b/kmymoney2/mymoney/storage/mymoneystoragebin.h
new file mode 100644
index 0000000..6ef7e20
--- /dev/null
+++ b/kmymoney2/mymoney/storage/mymoneystoragebin.h
@@ -0,0 +1,49 @@
+/***************************************************************************
+ imymoneystoragebin.h - description
+ -------------------
+ begin : Sun May 5 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYSTORAGEBIN_H
+#define MYMONEYSTORAGEBIN_H
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+/**
+ *@author Thomas Baumgart
+ */
+
+ #define VERSION_0_3_3 0x00000006 // MAGIC1 for version 0.33 files
+ #define VERSION_0_4_0 0x00000007 // MAGIC1 for version 0.4 files
+
+ #define MAGIC_0_50 0x4B4D794D // "KMyM" MAGIC1 for version 0.5 files
+ #define MAGIC_0_51 0x6F6E6579 // "oney" second part of MAGIC
+
+ #define VERSION_0_50 0x00000010 // Version 0.5 file version info
+ #define VERSION_0_51 0x00000011 // use 8 bytes for MyMoneyMoney objects
+
+ // add new definitions above and make sure to adapt MAX_FILE_VERSION below
+ #define MIN_FILE_VERSION VERSION_0_50
+ #define MAX_FILE_VERSION VERSION_0_51
+
+#endif
diff --git a/kmymoney2/mymoney/storage/mymoneystoragedump.cpp b/kmymoney2/mymoney/storage/mymoneystoragedump.cpp
new file mode 100644
index 0000000..e0d0083
--- /dev/null
+++ b/kmymoney2/mymoney/storage/mymoneystoragedump.cpp
@@ -0,0 +1,446 @@
+/***************************************************************************
+ mymoneystoragedump.cpp - description
+ -------------------
+ begin : Sun May 5 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstring.h>
+#include <qdatetime.h>
+#include <qvaluelist.h>
+#include <qstringlist.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "mymoneystoragedump.h"
+#include "imymoneystorage.h"
+#include "../mymoneyaccount.h"
+#include "../mymoneysecurity.h"
+#include "../mymoneyprice.h"
+
+MyMoneyStorageDump::MyMoneyStorageDump()
+{
+}
+
+MyMoneyStorageDump::~MyMoneyStorageDump()
+{
+}
+
+void MyMoneyStorageDump::readStream(QDataStream& /* s */, IMyMoneySerialize* /* storage */)
+{
+ qDebug("Reading not supported by MyMoneyStorageDump!!");
+}
+
+void MyMoneyStorageDump::writeStream(QDataStream& _s, IMyMoneySerialize* _storage)
+{
+ QTextStream s(_s.device());
+ IMyMoneyStorage* storage = dynamic_cast<IMyMoneyStorage *> (_storage);
+ MyMoneyPayee user = storage->user();
+
+ s << "File-Info\n";
+ s << "---------\n";
+ s << "user name = " << user.name() << "\n";
+ s << "user street = " << user.address() << "\n";
+ s << "user city = " << user.city() << "\n";
+ s << "user city = " << user.state() << "\n";
+ s << "user zip = " << user.postcode() << "\n";
+ s << "user telephone = " << user.telephone() << "\n";
+ s << "user e-mail = " << user.email() << "\n";
+ s << "creation date = " << storage->creationDate().toString(Qt::ISODate) << "\n";
+ s << "last modification date = " << storage->lastModificationDate().toString(Qt::ISODate) << "\n";
+ s << "base currency = " << storage->value("kmm-baseCurrency") << "\n";
+ s << "\n";
+
+ s << "Internal-Info\n";
+ s << "-------------\n";
+ QValueList<MyMoneyAccount> list_a;
+ storage->accountList(list_a);
+ s << "accounts = " << list_a.count() <<", next id = " << _storage->accountId() << "\n";
+ MyMoneyTransactionFilter filter;
+ filter.setReportAllSplits(false);
+ QValueList<MyMoneyTransaction> list_t;
+ storage->transactionList(list_t, filter);
+ QValueList<MyMoneyTransaction>::ConstIterator it_t;
+ s << "transactions = " << list_t.count() << ", next id = " << _storage->transactionId() << "\n";
+ QMap<int,int> xferCount;
+ for(it_t = list_t.begin(); it_t != list_t.end(); ++it_t) {
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+ int accountCount = 0;
+ for(it_s = (*it_t).splits().begin(); it_s != (*it_t).splits().end(); ++it_s) {
+ MyMoneyAccount acc = storage->account((*it_s).accountId());
+ if(acc.accountGroup() != MyMoneyAccount::Expense
+ && acc.accountGroup() != MyMoneyAccount::Income)
+ accountCount++;
+ }
+ if(accountCount > 1)
+ xferCount[accountCount] = xferCount[accountCount] + 1;
+ }
+ QMap<int,int>::ConstIterator it_cnt;
+ for(it_cnt = xferCount.begin(); it_cnt != xferCount.end(); ++it_cnt) {
+ s << " " << *it_cnt << " of them references " << it_cnt.key() << " accounts\n";
+ }
+
+ s << "payees = " << _storage->payeeList().count() << ", next id = " << _storage->payeeId() << "\n";
+ s << "institutions = " << _storage->institutionList().count() << ", next id = " << _storage->institutionId() << "\n";
+ s << "schedules = " << _storage->scheduleList().count() << ", next id = " << _storage->scheduleId() << "\n";
+ s << "\n";
+
+ s << "Institutions\n";
+ s << "------------\n";
+
+ QValueList<MyMoneyInstitution> list_i = storage->institutionList();
+ QValueList<MyMoneyInstitution>::ConstIterator it_i;
+ for(it_i = list_i.begin(); it_i != list_i.end(); ++it_i) {
+ s << " ID = " << (*it_i).id() << "\n";
+ s << " Name = " << (*it_i).name() << "\n";
+ s << "\n";
+ }
+ s << "\n";
+
+ s << "Payees" << "\n";
+ s << "------" << "\n";
+
+ QValueList<MyMoneyPayee> list_p = storage->payeeList();
+ QValueList<MyMoneyPayee>::ConstIterator it_p;
+ for(it_p = list_p.begin(); it_p != list_p.end(); ++it_p) {
+ s << " ID = " << (*it_p).id() << "\n";
+ s << " Name = " << (*it_p).name() << "\n";
+ s << " Address = " << (*it_p).address() << "\n";
+ s << " City = " << (*it_p).city() << "\n";
+ s << " State = " << (*it_p).state() << "\n";
+ s << " Zip = " << (*it_p).postcode() << "\n";
+ s << " E-Mail = " << (*it_p).email() << "\n";
+ s << " Telephone = " << (*it_p).telephone() << "\n";
+ s << " Reference = " << (*it_p).reference() << "\n";
+ s << "\n";
+ }
+ s << "\n";
+
+
+ s << "Accounts" << "\n";
+ s << "--------" << "\n";
+
+ list_a.push_front(storage->equity());
+ list_a.push_front(storage->expense());
+ list_a.push_front(storage->income());
+ list_a.push_front(storage->liability());
+ list_a.push_front(storage->asset());
+ QValueList<MyMoneyAccount>::ConstIterator it_a;
+ for(it_a = list_a.begin(); it_a != list_a.end(); ++it_a) {
+ s << " ID = " << (*it_a).id() << "\n";
+ s << " Name = " << (*it_a).name() << "\n";
+ s << " Number = " << (*it_a).number() << "\n";
+ s << " Description = " << (*it_a).description() << "\n";
+ s << " Type = " << (*it_a).accountType() << "\n";
+ if((*it_a).currencyId().isEmpty()) {
+ s << " Currency = unknown\n";
+ } else {
+ if((*it_a).isInvest()) {
+ s << " Equity = " << storage->security((*it_a).currencyId()).name() << "\n";
+ } else {
+ s << " Currency = " << storage->currency((*it_a).currencyId()).name() << "\n";
+ }
+ }
+ s << " Parent = " << (*it_a).parentAccountId();
+ if(!(*it_a).parentAccountId().isEmpty()) {
+ MyMoneyAccount parent = storage->account((*it_a).parentAccountId());
+ s << " (" << parent.name() << ")";
+ } else {
+ s << "n/a";
+ }
+ s << "\n";
+
+ s << " Institution = " << (*it_a).institutionId();
+ if(!(*it_a).institutionId().isEmpty()) {
+ MyMoneyInstitution inst = storage->institution((*it_a).institutionId());
+ s << " (" << inst.name() << ")";
+ } else {
+ s << "n/a";
+ }
+ s << "\n";
+
+ s << " Opening data = " << (*it_a).openingDate().toString(Qt::ISODate) << "\n";
+ s << " Last modified = " << (*it_a).lastModified().toString(Qt::ISODate) << "\n";
+ s << " Last reconciled = " << (*it_a).lastReconciliationDate().toString(Qt::ISODate) << "\n";
+ s << " Balance = " << (*it_a).balance().formatMoney("", 2) << "\n";
+
+ dumpKVP(" KVP: ", s, *it_a);
+ dumpKVP(" OnlineBankingSettings: ", s, (*it_a).onlineBankingSettings());
+
+ QStringList list_s = (*it_a).accountList();
+ QStringList::ConstIterator it_s;
+ if(list_s.count() > 0) {
+ s << " Children =" << "\n";
+ }
+ for(it_s = list_s.begin(); it_s != list_s.end(); ++it_s) {
+ MyMoneyAccount child = storage->account(*it_s);
+ s << " " << *it_s << " (" << child.name() << ")\n";
+ }
+ s << "\n";
+ }
+ s << "\n";
+
+#if 0
+ s << "Currencies" << "\n";
+ s << "----------" << "\n";
+
+ QValueList<MyMoneyCurrency> list_c = storage->currencyList();
+ QValueList<MyMoneyCurrency>::ConstIterator it_c;
+ for(it_c = list_c.begin(); it_c != list_c.end(); ++it_c) {
+ s << " Name = " << (*it_c).name() << "\n";
+ s << " ID = " << (*it_c).id() << "\n";
+ s << " Symbol = " << (*it_c).tradingSymbol() << "\n";
+ s << " Parts/Unit = " << (*it_c).partsPerUnit() << "\n";
+ s << " smallest cash fraction = " << (*it_c).smallestCashFraction() << "\n";
+ s << " smallest account fraction = " << (*it_c).smallestAccountFraction() << "\n";
+ dumpPriceHistory(s, (*it_c).priceHistory());
+ s << "\n";
+ }
+ s << "\n";
+#endif
+
+ s << "Securities" << "\n";
+ s << "----------" << "\n";
+
+ QValueList<MyMoneySecurity> list_e = storage->securityList();
+ QValueList<MyMoneySecurity>::ConstIterator it_e;
+ for(it_e = list_e.begin(); it_e != list_e.end(); ++it_e) {
+ s << " Name = " << (*it_e).name() << "\n";
+ s << " ID = " << (*it_e).id() << "\n";
+ s << " Market = " << (*it_e).tradingMarket() << "\n";
+ s << " Symbol = " << (*it_e).tradingSymbol() << "\n";
+ s << " Currency = " << (*it_e).tradingCurrency() << " (";
+ if((*it_e).tradingCurrency().isEmpty()) {
+ s << "unknown";
+ } else {
+ MyMoneySecurity tradingCurrency = storage->currency((*it_e).tradingCurrency());
+ if(!tradingCurrency.isCurrency()) {
+ s << "invalid currency: ";
+ }
+ s << tradingCurrency.name();
+ }
+ s << ")\n";
+
+ s << " Type = " << MyMoneySecurity::securityTypeToString((*it_e).securityType()) << "\n";
+ s << " smallest account fraction = " << (*it_e).smallestAccountFraction() << "\n";
+
+ s << " KVP: " << "\n";
+ QMap<QString, QString>kvp = (*it_e).pairs();
+ QMap<QString, QString>::Iterator it;
+ for(it = kvp.begin(); it != kvp.end(); ++it) {
+ s << " '" << it.key() << "' = '" << it.data() << "'\n";
+ }
+ s << "\n";
+ }
+ s << "\n";
+
+ s << "Prices" << "\n";
+ s << "--------" << "\n";
+
+ MyMoneyPriceList list_pr = _storage->priceList();
+ MyMoneyPriceList::ConstIterator it_pr;
+ for(it_pr = list_pr.begin(); it_pr != list_pr.end(); ++it_pr) {
+ s << " From = " << it_pr.key().first << "\n";
+ s << " To = " << it_pr.key().second << "\n";
+ MyMoneyPriceEntries::ConstIterator it_pre;
+ for(it_pre = (*it_pr).begin(); it_pre != (*it_pr).end(); ++it_pre) {
+ s << " Date = " << (*it_pre).date().toString() << "\n";
+ s << " Price = " << (*it_pre).rate(QString()).formatMoney("", 8) << "\n";
+ s << " Source = " << (*it_pre).source() << "\n";
+ s << " From = " << (*it_pre).from() << "\n";
+ s << " To = " << (*it_pre).to() << "\n";
+ }
+ s << "\n";
+ }
+ s << "\n";
+
+ s << "Transactions" << "\n";
+ s << "------------" << "\n";
+
+ for(it_t = list_t.begin(); it_t != list_t.end(); ++it_t) {
+ dumpTransaction(s, storage, *it_t);
+ }
+ s << "\n";
+
+
+ s << "Schedules" << "\n";
+ s << "---------" << "\n";
+
+ QValueList<MyMoneySchedule> list_s = storage->scheduleList();
+ QValueList<MyMoneySchedule>::ConstIterator it_s;
+ for(it_s = list_s.begin(); it_s != list_s.end(); ++it_s) {
+ s << " ID = " << (*it_s).id() << "\n";
+ s << " Name = " << (*it_s).name() << "\n";
+ s << " Startdate = " << (*it_s).startDate().toString(Qt::ISODate) << "\n";
+ if((*it_s).willEnd())
+ s << " Enddate = " << (*it_s).endDate().toString(Qt::ISODate) << "\n";
+ else
+ s << " Enddate = not specified\n";
+ s << " Occurence = " << (*it_s).occurenceToString() << "\n";
+ s << " OccurenceMultiplier = " << (*it_s).occurenceMultiplier() << "\n";
+ s << " Type = " << MyMoneySchedule::scheduleTypeToString((*it_s).type()) << "\n";
+ s << " Paymenttype = " << MyMoneySchedule::paymentMethodToString((*it_s).paymentType()) << "\n";
+ s << " Fixed = " << (*it_s).isFixed() << "\n";
+ s << " AutoEnter = " << (*it_s).autoEnter() << "\n";
+
+ if((*it_s).lastPayment().isValid())
+ s << " Last payment = " << (*it_s).lastPayment().toString(Qt::ISODate) << "\n";
+ else
+ s << " Last payment = not defined" << "\n";
+ if((*it_s).isFinished())
+ s << " Next payment = payment finished" << "\n";
+ else {
+ s << " Next payment = " << (*it_s).nextDueDate().toString(Qt::ISODate) << "\n";
+ if((*it_s).isOverdue())
+ s << " = overdue!" << "\n";
+ }
+
+ QValueList<QDate> list_d;
+ QValueList<QDate>::ConstIterator it_d;
+
+ list_d = (*it_s).recordedPayments();
+ if(list_d.count() > 0) {
+ s << " Recorded payments" << "\n";
+ for(it_d = list_d.begin(); it_d != list_d.end(); ++it_d) {
+ s << " " << (*it_d).toString(Qt::ISODate) << "\n";
+ }
+ }
+ s << " TRANSACTION\n";
+ dumpTransaction(s, storage, (*it_s).transaction());
+ }
+ s << "\n";
+
+ s << "Reports" << "\n";
+ s << "-------" << "\n";
+
+ QValueList<MyMoneyReport> list_r = storage->reportList();
+ QValueList<MyMoneyReport>::ConstIterator it_r;
+ for(it_r = list_r.begin(); it_r != list_r.end(); ++it_r) {
+ s << " ID = " << (*it_r).id() << "\n";
+ s << " Name = " << (*it_r).name() << "\n";
+ }
+}
+
+void MyMoneyStorageDump::dumpKVP(const QString& headline, QTextStream& s, const MyMoneyKeyValueContainer &kvp, int indent)
+{
+ QString ind;
+ ind.fill(' ', indent);
+ s << ind << headline << "\n";
+ QMap<QString, QString>::const_iterator it;
+ for(it = kvp.pairs().begin(); it != kvp.pairs().end(); ++it) {
+ s << ind << " '" << it.key() << "' = '" << it.data() << "'\n";
+ }
+}
+
+void MyMoneyStorageDump::dumpTransaction(QTextStream& s, IMyMoneyStorage* storage, const MyMoneyTransaction& it_t)
+{
+ s << " ID = " << it_t.id() << "\n";
+ s << " Postdate = " << it_t.postDate().toString(Qt::ISODate) << "\n";
+ s << " EntryDate = " << it_t.entryDate().toString(Qt::ISODate) << "\n";
+ s << " Commodity = [" << it_t.commodity() << "]\n";
+ s << " Memo = " << it_t.memo() << "\n";
+ s << " BankID = " << it_t.bankID() << "\n";
+ dumpKVP("KVP:", s, it_t, 2);
+
+ s << " Splits\n";
+ s << " ------\n";
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+ for(it_s = it_t.splits().begin(); it_s != it_t.splits().end(); ++it_s) {
+ s << " ID = " << (*it_s).id() << "\n";
+ s << " Transaction = " << (*it_s).transactionId() << "\n";
+ s << " Payee = " << (*it_s).payeeId();
+ if(!(*it_s).payeeId().isEmpty()) {
+ MyMoneyPayee p = storage->payee((*it_s).payeeId());
+ s << " (" << p.name() << ")" << "\n";
+ } else
+ s << " ()\n";
+ s << " Account = " << (*it_s).accountId();
+ MyMoneyAccount acc;
+ try {
+ acc = storage->account((*it_s).accountId());
+ s << " (" << acc.name() << ") [" << acc.currencyId() << "]\n";
+ } catch (MyMoneyException *e) {
+ s << " (---) [---]\n";
+ delete e;
+ }
+ s << " Memo = " << (*it_s).memo() << "\n";
+ if((*it_s).value() == MyMoneyMoney::autoCalc)
+ s << " Value = will be calculated" << "\n";
+ else
+ s << " Value = " << (*it_s).value().formatMoney("", 2)
+ << " (" << (*it_s).value().toString() << ")\n";
+ s << " Shares = " << (*it_s).shares().formatMoney("", 2)
+ << " (" << (*it_s).shares().toString() << ")\n";
+ s << " Action = '" << (*it_s).action() << "'\n";
+ s << " Nr = '" << (*it_s).number() << "'\n";
+ s << " ReconcileFlag = '" << reconcileToString((*it_s).reconcileFlag()) << "'\n";
+ if((*it_s).reconcileFlag() != MyMoneySplit::NotReconciled) {
+ s << " ReconcileDate = " << (*it_s).reconcileDate().toString(Qt::ISODate) << "\n";
+ }
+ s << " BankID = " << (*it_s).bankID() << "\n";
+ dumpKVP("KVP:", s, (*it_s), 4);
+ s << "\n";
+ }
+ s << "\n";
+}
+
+#define i18n QString
+
+const QString MyMoneyStorageDump::reconcileToString(MyMoneySplit::reconcileFlagE flag) const
+{
+ QString rc;
+
+ switch(flag) {
+ case MyMoneySplit::NotReconciled:
+ rc = i18n("not reconciled");
+ break;
+ case MyMoneySplit::Cleared:
+ rc = i18n("cleared");
+ break;
+ case MyMoneySplit::Reconciled:
+ rc = i18n("reconciled");
+ break;
+ case MyMoneySplit::Frozen:
+ rc = i18n("frozen");
+ break;
+ default:
+ rc = i18n("unknown");
+ break;
+ }
+ return rc;
+}
+
+#if 0
+void MyMoneyStorageDump::dumpPriceHistory(QTextStream& s, const equity_price_history history)
+{
+ if(history.count() != 0) {
+ s << " Price History:\n";
+
+ equity_price_history::const_iterator it_price = history.begin();
+ while ( it_price != history.end() )
+ {
+ s << " " << it_price.key().toString() << ": " << it_price.data().toDouble() << "\n";
+ it_price++;
+ }
+ }
+}
+#endif
diff --git a/kmymoney2/mymoney/storage/mymoneystoragedump.h b/kmymoney2/mymoney/storage/mymoneystoragedump.h
new file mode 100644
index 0000000..e399cde
--- /dev/null
+++ b/kmymoney2/mymoney/storage/mymoneystoragedump.h
@@ -0,0 +1,56 @@
+/***************************************************************************
+ mymoneystoragedump.h - description
+ -------------------
+ begin : Sun May 5 2002
+ copyright : (C) 2000-2002 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYSTORAGEDUMP_H
+#define MYMONEYSTORAGEDUMP_H
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qdatastream.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "imymoneyserialize.h"
+#include "../mymoneysecurity.h"
+
+/**
+ * @author Thomas Baumgart
+ */
+
+class MyMoneyStorageDump
+{
+public:
+ MyMoneyStorageDump();
+ ~MyMoneyStorageDump();
+
+ void readStream(QDataStream& s, IMyMoneySerialize* storage);
+ void writeStream(QDataStream& s, IMyMoneySerialize* storage);
+
+private:
+ void dumpTransaction(QTextStream& s, IMyMoneyStorage* storage, const MyMoneyTransaction& it_t);
+ void dumpKVP(const QString& headline, QTextStream& s, const MyMoneyKeyValueContainer &kvp, int indent = 0);
+ const QString reconcileToString(MyMoneySplit::reconcileFlagE flag) const;
+};
+
+#endif
diff --git a/kmymoney2/mymoney/storage/mymoneystoragesql.cpp b/kmymoney2/mymoney/storage/mymoneystoragesql.cpp
new file mode 100644
index 0000000..97b4c55
--- /dev/null
+++ b/kmymoney2/mymoney/storage/mymoneystoragesql.cpp
@@ -0,0 +1,4511 @@
+/***************************************************************************
+ mymoneystoragesql.cpp
+ ---------------------
+ begin : 11 November 2005
+ copyright : (C) 2005 by Tony Bloomfield
+ email : tonybloom@users.sourceforge.net
+ : Fernando Vilas <fvilas@iname.com>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 <algorithm>
+#include <numeric>
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstring.h>
+#include <qdatetime.h>
+#include <qvaluelist.h>
+#include <qstringlist.h>
+#include <qiodevice.h>
+#include <qsqldriver.h>
+
+// ----------------------------------------------------------------------------
+// KDE Includes
+
+#include <klocale.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "mymoneystoragesql.h"
+#include "imymoneyserialize.h"
+#include <kmymoney/kmymoneyglobalsettings.h>
+
+#define TRY try {
+#define CATCH } catch (MyMoneyException *e) {
+#define PASS } catch (MyMoneyException *e) { throw; }
+#define ECATCH }
+#define DBG(a) // qDebug(a)
+//#define TRACE(a) qDebug(a)
+#define TRACE(a) ::timetrace(a)
+
+//***************** THE CURRENT VERSION OF THE DATABASE LAYOUT ****************
+unsigned int MyMoneyDbDef::m_currentVersion = 6;
+
+// subclass QSqlQuery for performance tracing
+
+MyMoneySqlQuery::MyMoneySqlQuery (MyMoneyStorageSql* db)
+ : QSqlQuery (static_cast<QSqlDatabase*>(db)) {
+ m_db = db;
+}
+
+bool MyMoneySqlQuery::exec () {
+ TRACE(QString("start sql - %1").arg(lastQuery()));
+ bool rc = QSqlQuery::exec();
+ QString msg("end sql\n%1\n***Query returned %2, row count %3");
+ TRACE (msg.arg(QSqlQuery::executedQuery()).arg(rc).arg(numRowsAffected()));
+ //DBG (QString("%1\n***Query returned %2, row count %3").arg(QSqlQuery::executedQuery()).arg(rc).arg(size()));
+ return (rc);
+}
+
+bool MyMoneySqlQuery::prepare ( const QString & query ) {
+ if (m_db->isSqlite3()) {
+ QString newQuery = query;
+ return (QSqlQuery::prepare (newQuery.replace("FOR UPDATE", "")));
+ }
+ return (QSqlQuery::prepare (query));
+}
+
+//*****************************************************************************
+MyMoneyDbDrivers::MyMoneyDbDrivers () {
+ m_driverMap["QDB2"] = QString("IBM DB2");
+ m_driverMap["QIBASE"] = QString("Borland Interbase");
+ m_driverMap["QMYSQL3"] = QString("MySQL");
+ m_driverMap["QOCI8"] = QString("Oracle Call Interface, version 8 and 9");
+ m_driverMap["QODBC3"] = QString("Open Database Connectivity");
+ m_driverMap["QPSQL7"] = QString("PostgreSQL v6.x and v7.x");
+ m_driverMap["QTDS7"] = QString("Sybase Adaptive Server and Microsoft SQL Server");
+#if QT_VERSION < 0x040000
+ m_driverMap["QSQLITE3"] = QString("SQLite Version 3");
+#else
+ m_driverMap["QSQLITE"] = QString("SQLite Version 3");
+#endif
+}
+
+databaseTypeE MyMoneyDbDrivers::driverToType (const QString& driver) const {
+ if (driver == "QDB2") return(Db2);
+ else if (driver == "QIBASE") return(Interbase);
+ else if (driver == "QMYSQL3") return(Mysql);
+ else if (driver == "QOCI8") return(Oracle8);
+ else if (driver == "QODBC3") return(ODBC3);
+ else if (driver == "QPSQL7") return(Postgresql);
+ else if (driver == "QTDS7") return(Sybase);
+#if QT_VERSION < 0x040000
+ else if (driver == "QSQLITE3") return(Sqlite3);
+#else
+ else if (driver == "QSQLITE") return(Sqlite3);
+#endif
+ else throw new MYMONEYEXCEPTION (QString("Unknown database driver type").arg(driver));
+}
+
+bool MyMoneyDbDrivers::isTested (databaseTypeE dbType) const {
+ switch (dbType) {
+ case Mysql:
+ case Sqlite3:
+ case Postgresql:
+ return (true);
+ default:
+ return(false);
+ }
+ return(false);
+}
+
+//************************ Constructor/Destructor *****************************
+MyMoneyStorageSql::MyMoneyStorageSql (IMyMoneySerialize *storage, const KURL& url)
+ : QSqlDatabase (url.queryItem("driver"), QString("kmmdatabase")) {
+ DBG("*** Entering MyMoneyStorageSql::MyMoneyStorageSql");
+ m_dbVersion = 0;
+ m_progressCallback = 0;
+ m_displayStatus = false;
+ m_storage = storage;
+ m_storagePtr = dynamic_cast<IMyMoneyStorage*>(storage);
+ m_newDatabase = false;
+ m_readingPrices = false;
+ m_loadAll = false;
+ m_override = false;
+ m_preferred.setReportAllSplits(false);
+}
+
+int MyMoneyStorageSql::open(const KURL& url, int openMode, bool clear) {
+ DBG("*** Entering MyMoneyStorageSql::open");
+try {
+ int rc = 0;
+ QString driverName = url.queryItem("driver");
+ m_dbType = m_drivers.driverToType(driverName);
+ //get the input options
+ QStringList options = QStringList::split(',', url.queryItem("options"));
+ m_loadAll = options.contains("loadAll")/*|| m_mode == 0*/;
+ m_override = options.contains("override");
+
+ // create the database connection
+ QString dbName = url.path().right(url.path().length() - 1); // remove separator slash
+ setDatabaseName(dbName);
+ setHostName(url.host());
+ setUserName(url.user());
+ setPassword(url.pass());
+ switch (openMode) {
+ case IO_ReadOnly: // OpenDatabase menu entry (or open last file)
+ case IO_ReadWrite: // Save menu entry with database open
+ if (!QSqlDatabase::open()) {
+ buildError(MyMoneySqlQuery(), __func__, "opening database");
+ rc = 1;
+ } else {
+ rc = createTables(); // check all tables are present, create if not (we may add tables at some time)
+ }
+ break;
+ case IO_WriteOnly: // SaveAs Database - if exists, must be empty, if not will create
+ // Try to open the database.
+ // If that fails, try to create the database, then try to open it again.
+ m_newDatabase = true;
+ if (!QSqlDatabase::open()) {
+ if (createDatabase(url) != 0) {
+ rc = 1;
+ } else {
+ if (!QSqlDatabase::open()) {
+ buildError(MyMoneySqlQuery(), __func__, "opening new database");
+ rc = 1;
+ } else {
+ rc = createTables();
+ }
+ }
+ } else {
+ rc = createTables();
+ if (rc == 0) {
+ if (clear) {
+ clean();
+ } else {
+ rc = isEmpty();
+ }
+ }
+ }
+ break;
+ default:
+ qFatal("%s", QString("%1 - unknown open mode %2").arg(__func__).arg(openMode).data());
+ }
+ if (rc != 0) return (rc);
+ // bypass logon check if we are creating a database
+ if (openMode == IO_WriteOnly) return(0);
+ // check if the database is locked, if not lock it
+ readFileInfo();
+ if (!m_logonUser.isEmpty() && (!m_override)) {
+ m_error = QString
+ (i18n("Database apparently in use\nOpened by %1 on %2 at %3.\nOpen anyway?"))
+ .arg(m_logonUser)
+ .arg(m_logonAt.date().toString(Qt::ISODate))
+ .arg(m_logonAt.time().toString("hh.mm.ss"));
+ qDebug("%s", m_error.data());
+ close(false);
+ rc = -1;
+ } else {
+ m_logonUser = url.user() + "@" + url.host();
+ m_logonAt = QDateTime::currentDateTime();
+ writeFileInfo();
+ }
+ return(rc);
+} catch (QString& s) {
+ qDebug("%s",s.data());
+ return (1);
+}
+}
+
+void MyMoneyStorageSql::close(bool logoff) {
+ DBG("*** Entering MyMoneyStorageSql::close");
+ if (QSqlDatabase::open()) {
+ if (logoff) {
+ startCommitUnit(__func__);
+ m_logonUser = QString();
+ writeFileInfo();
+ endCommitUnit(__func__);
+ }
+ QSqlDatabase::close();
+ QSqlDatabase::removeDatabase(this);
+ }
+}
+
+int MyMoneyStorageSql::createDatabase (const KURL& url) {
+ DBG("*** Entering MyMoneyStorageSql::createDatabase");
+ if (m_dbType == Sqlite3) return(0); // not needed for sqlite
+ if (!m_dbType == Mysql) {
+ m_error =
+ QString(i18n("Cannot currently create database for driver %1; please create manually")).arg(driverName());
+ return (1);
+ }
+ // create the database (only works for mysql at present)
+ QString dbName = url.path().right(url.path().length() - 1); // remove separator slash
+ QSqlDatabase *maindb = QSqlDatabase::addDatabase(driverName());
+ maindb->setDatabaseName ("mysql");
+ maindb->setHostName (url.host());
+ maindb->setUserName (url.user());
+ maindb->setPassword (url.pass());
+ maindb->open();
+ QSqlQuery qm(maindb);
+ QString qs = QString("CREATE DATABASE %1;").arg(dbName);
+ qm.prepare (qs);
+ if (!qm.exec()) {
+ buildError (qm, __func__, QString(i18n("Error in create database %1; do you have create permissions?")).arg(dbName));
+ return (1);
+ }
+ QSqlDatabase::removeDatabase (maindb);
+ return (0);
+}
+
+
+int MyMoneyStorageSql::upgradeDb() {
+ DBG("*** Entering MyMoneyStorageSql::upgradeDb");
+ //signalProgress(0, 1, QObject::tr("Upgrading database..."));
+ MyMoneySqlQuery q(this);
+ q.prepare ("SELECT version FROM kmmFileInfo;");
+ if (!q.exec() || !q.next()) {
+ if (!m_newDatabase) {
+ buildError (q, __func__, "Error retrieving file info(version)");
+ return(1);
+ } else {
+ m_dbVersion = m_db.currentVersion();
+ m_storage->setFileFixVersion(m_storage->currentFixVersion());
+ QSqlQuery q(this);
+ q.prepare("UPDATE kmmFileInfo SET version = :version, \
+ fixLevel = :fixLevel;");
+ q.bindValue(":version", m_dbVersion);
+ q.bindValue(":fixLevel", m_storage->currentFixVersion());
+ if (!q.exec()) {
+ buildError (q, __func__, "Error updating file info(version)");
+ return(1);
+ }
+ return (0);
+ }
+ }
+ // prior to dbv6, 'version' format was 'dbversion.fixLevel+1'
+ // as of dbv6, these are separate fields
+ QString version = q.value(0).toString();
+ if (version.contains('.')) {
+ m_dbVersion = q.value(0).toString().section('.', 0, 0).toUInt();
+ m_storage->setFileFixVersion(q.value(0).toString().section('.', 1, 1).toUInt() - 1);
+ } else {
+ m_dbVersion = version.toUInt();
+ q.prepare ("SELECT fixLevel FROM kmmFileInfo;");
+ if (!q.exec() || !q.next()) {
+ buildError (q, __func__, "Error retrieving file info (fixLevel)");
+ return(1);
+ }
+ m_storage->setFileFixVersion(q.value(0).toUInt());
+ }
+ int rc = 0;
+ while ((m_dbVersion < m_db.currentVersion()) && (rc == 0)) {
+ switch (m_dbVersion) {
+ case 0:
+ if ((rc = upgradeToV1()) != 0) return (1);
+ ++m_dbVersion;
+ break;
+ case 1:
+ if ((rc = upgradeToV2()) != 0) return (1);
+ ++m_dbVersion;
+ break;
+ case 2:
+ if ((rc = upgradeToV3()) != 0) return (1);
+ ++m_dbVersion;
+ break;
+ case 3:
+ if ((rc = upgradeToV4()) != 0) return (1);
+ ++m_dbVersion;
+ break;
+ case 4:
+ if ((rc = upgradeToV5()) != 0) return (1);
+ ++m_dbVersion;
+ break;
+ case 5:
+ if ((rc = upgradeToV6()) != 0) return (1);
+ ++m_dbVersion;
+ break;
+ case 6:
+ break;
+ default:
+ qFatal("Unknown version number in database - %d", m_dbVersion);
+ }
+ }
+ // write updated version to DB
+ //setVersion(QString("%1.%2").arg(m_dbVersion).arg(m_minorVersion));
+ q.prepare (QString("UPDATE kmmFileInfo SET version = :version;"));
+ q.bindValue(":version", m_dbVersion);
+ if (!q.exec()) {
+ buildError (q, __func__, "Error updating db version");
+ return (1);
+ }
+ //signalProgress(-1,-1);
+ return (0);
+}
+// SF bug 2779291
+// check whether a column appears in a table already; if not, add it
+bool MyMoneyStorageSql::addColumn
+ (const QString& table, const QString& col,
+ const QString& after)
+{
+ MyMoneyDbTable t = m_db.m_tables[table];
+ MyMoneyDbTable::field_iterator ft;
+ const MyMoneyDbColumn* c;
+ for (ft = t.begin(); ft != t.end(); ++ft) {
+ c = (*ft);
+ if (c->name() == col)
+ break;
+ }
+ if (ft == t.end()) qFatal("addColumn - get it right");
+ return (addColumn(t, *c, after));
+}
+
+bool MyMoneyStorageSql::addColumn
+ (const MyMoneyDbTable& t, const MyMoneyDbColumn& c,
+ const QString& after){
+ if ((m_dbType == Sqlite3) && (!after.isEmpty()))
+ qFatal("sqlite doesn't support 'AFTER'; use sqliteAlterTable");
+ if (record(t.name()).contains(c.name()))
+ return (true);
+ QSqlQuery q(this);
+ QString afterString = ";";
+ if (!after.isEmpty())
+ afterString = QString("AFTER %1;").arg(after);
+ q.prepare("ALTER TABLE " + t.name() + " ADD COLUMN " +
+ c.generateDDL(m_dbType) + afterString);
+ if (!q.exec()) {
+ buildError (q, __func__,
+ QString("Error adding column %1 to table %2").arg(c.name()).arg(t.name()));
+ return (false);
+ }
+ return (true);
+}
+
+// analogous to above
+bool MyMoneyStorageSql::dropColumn
+ (const QString& table, const QString& col)
+{
+ return (dropColumn(m_db.m_tables[table], col));
+}
+
+bool MyMoneyStorageSql::dropColumn
+ (const MyMoneyDbTable& t, const QString& col){
+ if (m_dbType == Sqlite3)
+ qFatal("sqlite doesn't support 'DROP COLUMN'; use sqliteAlterTable");
+ if (!record(t.name()).contains(col))
+ return (true);
+ QSqlQuery q(this);
+ q.prepare("ALTER TABLE " + t.name() + " DROP COLUMN "
+ + col + ";");
+ if (!q.exec()) {
+ buildError (q, __func__,
+ QString("Error dropping column %1 from table %2").arg(col).arg(t.name()));
+ return (false);
+ }
+ return (true);
+}
+
+int MyMoneyStorageSql::upgradeToV1() {
+ DBG("*** Entering MyMoneyStorageSql::upgradeToV1");
+ if ((m_dbType == Sqlite) || (m_dbType == Sqlite3)) qFatal("SQLite upgrade NYI");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ // change kmmSplits pkey to (transactionId, splitId)
+ q.prepare ("ALTER TABLE kmmSplits ADD PRIMARY KEY (transactionId, splitId);");
+ if (!q.exec()) {
+ buildError (q, __func__, "Error updating kmmSplits pkey");
+ return (1);
+ }
+ // change kmmSplits alter checkNumber varchar(32)
+ q.prepare (m_db.m_tables["kmmSplits"].modifyColumnString(m_dbType, "checkNumber",
+ MyMoneyDbColumn("checkNumber", "varchar(32)")));
+ if (!q.exec()) {
+ buildError (q, __func__, "Error expanding kmmSplits.checkNumber");
+ return (1);
+ }
+ // change kmmSplits add postDate datetime
+ if (!addColumn(m_db.m_tables["kmmSplits"],
+ MyMoneyDbDatetimeColumn("postDate")))
+ return (1);
+ // initialize it to same value as transaction (do it the long way round)
+ q.prepare ("SELECT id, postDate FROM kmmTransactions WHERE txType = 'N';");
+ if (!q.exec()) {
+ buildError (q, __func__, "Error priming kmmSplits.postDate");
+ return (1);
+ }
+ QMap<QString, QDateTime> tids;
+ while (q.next()) tids[q.value(0).toString()] = q.value(1).toDateTime();
+ QMap<QString, QDateTime>::ConstIterator it;
+ for (it = tids.begin(); it != tids.end(); ++it) {
+ q.prepare ("UPDATE kmmSplits SET postDate=:postDate WHERE transactionId = :id;");
+ q.bindValue(":postDate", it.data().toString(Qt::ISODate));
+ q.bindValue(":id", it.key());
+ if (!q.exec()) {
+ buildError (q, __func__, "priming kmmSplits.postDate");
+ return(1);
+ }
+ }
+ // add index to kmmKeyValuePairs to (kvpType,kvpId)
+ QStringList list;
+ list << "kvpType" << "kvpId";
+ q.prepare (MyMoneyDbIndex("kmmKeyValuePairs", "kmmKVPtype_id", list, false).generateDDL(m_dbType) + ";");
+ if (!q.exec()) {
+ buildError (q, __func__, "Error adding kmmKeyValuePairs index");
+ return (1);
+ }
+ // add index to kmmSplits to (accountId, txType)
+ list.clear();
+ list << "accountId" << "txType";
+ q.prepare (MyMoneyDbIndex("kmmSplits", "kmmSplitsaccount_type", list, false).generateDDL(m_dbType) + ";");
+ if (!q.exec()) {
+ buildError (q, __func__, "Error adding kmmSplits index");
+ return (1);
+ }
+ // change kmmSchedulePaymentHistory pkey to (schedId, payDate)
+ q.prepare ("ALTER TABLE kmmSchedulePaymentHistory ADD PRIMARY KEY (schedId, payDate);");
+ if (!q.exec()) {
+ buildError (q, __func__, "Error updating kmmSchedulePaymentHistory pkey");
+ return (1);
+ }
+ // change kmmPrices pkey to (fromId, toId, priceDate)
+ q.prepare ("ALTER TABLE kmmPrices ADD PRIMARY KEY (fromId, toId, priceDate);");
+ if (!q.exec()) {
+ buildError (q, __func__, "Error updating kmmPrices pkey");
+ return (1);
+ }
+ // change kmmReportConfig pkey to (name)
+ // There wasn't one previously, so no need to drop it.
+ q.prepare ("ALTER TABLE kmmReportConfig ADD PRIMARY KEY (name);");
+ if (!q.exec()) {
+ buildError (q, __func__, "Error updating kmmReportConfig pkey");
+ return (1);
+ }
+ // change kmmFileInfo add budgets unsigned bigint after kvps
+ if (!addColumn(m_db.m_tables["kmmFileInfo"],
+ MyMoneyDbIntColumn("budgets", MyMoneyDbIntColumn::BIG, false)))
+ return (1);
+ // change kmmFileInfo add hiBudgetId unsigned bigint after hiReportId
+ if (!addColumn(m_db.m_tables["kmmFileInfo"],
+ MyMoneyDbIntColumn("hiBudgetId", MyMoneyDbIntColumn::BIG, false)))
+ return (1);
+ // change kmmFileInfo add logonUser
+ if (!addColumn(m_db.m_tables["kmmFileInfo"],
+ MyMoneyDbColumn("logonUser", "varchar(255)", false)))
+ return (1);
+ // change kmmFileInfo add logonAt datetime
+ if (!addColumn(m_db.m_tables["kmmFileInfo"],
+ MyMoneyDbDatetimeColumn("logonAt", false)))
+ return (1);
+ // change kmmAccounts add transactionCount unsigned bigint as last field
+ if (!addColumn(m_db.m_tables["kmmAccounts"],
+ MyMoneyDbIntColumn("transactionCount", MyMoneyDbIntColumn::BIG, false)))
+ return (1);
+ // calculate the transaction counts. the application logic defines an account's tx count
+ // in such a way as to count multiple splits in a tx which reference the same account as one.
+ // this is the only way I can think of to do this which will work in sqlite too.
+ // inefficient, but it only gets done once...
+ // get a list of all accounts so we'll get a zero value for those without txs
+ q.prepare ("SELECT id FROM kmmAccounts");
+ if (!q.exec()) {
+ buildError (q, __func__, "Error retrieving accounts for transaction counting");
+ return(1);
+ }
+ while (q.next()) {
+ m_transactionCountMap[q.value(0).toCString()] = 0;
+ }
+ q.prepare ("SELECT accountId, transactionId FROM kmmSplits WHERE txType = 'N' ORDER BY 1, 2");
+ if (!q.exec()) {
+ buildError (q, __func__, "Error retrieving splits for transaction counting");
+ return(1);
+ }
+ QString lastAcc, lastTx;
+ while (q.next()) {
+ QString thisAcc = q.value(0).toCString();
+ QString thisTx = q.value(1).toCString();
+ if ((thisAcc != lastAcc) || (thisTx != lastTx)) ++m_transactionCountMap[thisAcc];
+ lastAcc = thisAcc;
+ lastTx = thisTx;
+ }
+ QMap<QString, unsigned long>::ConstIterator itm;
+ q.prepare("UPDATE kmmAccounts SET transactionCount = :txCount WHERE id = :id;");
+ for (itm = m_transactionCountMap.begin(); itm != m_transactionCountMap.end(); ++itm) {
+ q.bindValue (":txCount", QString::number(itm.data()));
+ q.bindValue (":id", itm.key());
+ if (!q.exec()) {
+ buildError(q, __func__, "Error updating transaction count");
+ return (1);
+ }
+ }
+ m_transactionCountMap.clear();
+ // there were considerable problems with record counts in V0, so rebuild them
+ readFileInfo();
+ m_institutions = getRecCount("kmmInstitutions");
+ m_accounts = getRecCount("kmmAccounts");
+ m_payees = getRecCount("kmmPayees");
+ m_transactions = getRecCount("kmmTransactions WHERE txType = 'N'");
+ m_splits = getRecCount("kmmSplits");
+ m_securities = getRecCount("kmmSecurities");
+ m_prices = getRecCount("kmmPrices");
+ m_currencies = getRecCount("kmmCurrencies");
+ m_schedules = getRecCount("kmmSchedules");
+ m_reports = getRecCount("kmmReportConfig");
+ m_kvps = getRecCount("kmmKeyValuePairs");
+ m_budgets = getRecCount("kmmBudgetConfig");
+ writeFileInfo();
+ /* if sqlite {
+ q.prepare("VACUUM;");
+ if (!q.exec()) {
+ buildError (q, __func__, "Error vacuuming database");
+ return(1);
+ }
+ }*/
+ endCommitUnit(__func__);
+ return (0);
+}
+
+int MyMoneyStorageSql::upgradeToV2() {
+ DBG("*** Entering MyMoneyStorageSql::upgradeToV2");
+ //SQLite3 now supports ALTER TABLE...ADD COLUMN, so only die if version < 3
+ //if (m_dbType == Sqlite3) qFatal("SQLite upgrade NYI");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ // change kmmSplits add price fields
+ if (!addColumn(m_db.m_tables["kmmSplits"],
+ MyMoneyDbTextColumn("price")))
+ return (1);
+ if (!addColumn(m_db.m_tables["kmmSplits"],
+ MyMoneyDbTextColumn("priceFormatted")))
+ return (1);
+ endCommitUnit(__func__);
+ return (0);
+}
+
+int MyMoneyStorageSql::upgradeToV3() {
+ DBG("*** Entering MyMoneyStorageSql::upgradeToV3");
+ //SQLite3 now supports ALTER TABLE...ADD COLUMN, so only die if version < 3
+ //if (m_dbType == Sqlite3) qFatal("SQLite upgrade NYI");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ // The default value is given here to populate the column.
+ q.prepare ("ALTER TABLE kmmSchedules ADD COLUMN " +
+ MyMoneyDbIntColumn("occurenceMultiplier",
+ MyMoneyDbIntColumn::SMALL, false, false, true)
+ .generateDDL(m_dbType) + " DEFAULT 0;");
+ if (!q.exec()) {
+ buildError (q, __func__, "Error adding kmmSchedules.occurenceMultiplier");
+ return (1);
+ }
+ //The default is less than any useful value, so as each schedule is hit, it will update
+ //itself to the appropriate value.
+ endCommitUnit(__func__);
+ return 0;
+}
+
+int MyMoneyStorageSql::upgradeToV4() {
+ DBG("*** Entering MyMoneyStorageSql::upgradeToV4");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ QStringList list;
+ list << "transactionId" << "splitId";
+ q.prepare (MyMoneyDbIndex("kmmSplits", "kmmTx_Split", list, false).generateDDL(m_dbType) + ";");
+ if (!q.exec()) {
+ buildError (q, __func__, "Error adding kmmSplits index on (transactionId, splitId)");
+ return (1);
+ }
+ endCommitUnit(__func__);
+ return 0;
+}
+
+int MyMoneyStorageSql::upgradeToV5() {
+ DBG("*** Entering MyMoneyStorageSql::upgradeToV5");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ if (!addColumn(m_db.m_tables["kmmSplits"],
+ MyMoneyDbTextColumn("bankId")))
+ return (1);
+ if (!addColumn(m_db.m_tables["kmmPayees"],
+ MyMoneyDbTextColumn("notes", MyMoneyDbTextColumn::LONG)))
+ return (1);
+ if (!addColumn(m_db.m_tables["kmmPayees"],
+ MyMoneyDbColumn("defaultAccountId", "varchar(32)")))
+ return (1);
+ if (!addColumn(m_db.m_tables["kmmPayees"],
+ MyMoneyDbIntColumn("matchData", MyMoneyDbIntColumn::TINY,
+ false)))
+ return (1);
+ if (!addColumn(m_db.m_tables["kmmPayees"],
+ MyMoneyDbColumn("matchIgnoreCase", "char(1)")))
+ return (1);
+ if (!addColumn(m_db.m_tables["kmmPayees"],
+ MyMoneyDbTextColumn("matchKeys")))
+ return (1);
+ const MyMoneyDbTable& t = m_db.m_tables["kmmReportConfig"];
+ if (m_dbType != Sqlite3) {
+ q.prepare (t.dropPrimaryKeyString(m_dbType));
+ if (!q.exec()) {
+ buildError (q, __func__, "Error dropping Report table keys");
+ return (1);
+ }
+ } else {
+ if (!sqliteAlterTable(t))
+ return (1);
+ }
+ endCommitUnit(__func__);
+ return 0;
+}
+
+int MyMoneyStorageSql::upgradeToV6() {
+ DBG("*** Entering MyMoneyStorageSql::upgradeToV6");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ // add separate fix level in file info
+ if (!addColumn("kmmFileInfo", "fixLevel"))
+ return (1);
+ // upgrade Mysql to InnoDB transaction-safe engine
+ if (m_dbType == Mysql) {
+ for (QMapConstIterator<QString, MyMoneyDbTable> tt = m_db.tableBegin(); tt != m_db.tableEnd(); ++tt) {
+ q.prepare(QString("ALTER TABLE %1 ENGINE = InnoDB;").arg(tt.data().name()));
+ if (!q.exec()) {
+ buildError (q, __func__, "Error updating to InnoDB");
+ return (1);
+ }
+ }
+ }
+ // add unique id to reports table
+ if (!addColumn(m_db.m_tables["kmmReportConfig"],
+ MyMoneyDbColumn("id", "varchar(32)")))
+ return(1);
+ // read and write reports to get ids inserted
+ readFileInfo();
+ QMap<QString, MyMoneyReport> reportList =
+ fetchReports();
+ // the V5 database allowed lots of duplicate reports with no
+ // way to distinguish between them. The fetchReports call
+ // will have effectively removed all duplicates
+ // so we now delete from the db and re-write them
+ q.prepare("DELETE FROM kmmReportConfig;");
+ if (!q.exec()) {
+ buildError (q, __func__, "Error deleting reports");
+ return (1);
+ }
+ unsigned long long hiReportId = 0;
+ QMap<QString, MyMoneyReport>::const_iterator it_r;
+ for(it_r = reportList.begin(); it_r != reportList.end(); ++it_r) {
+ MyMoneyReport r = *it_r;
+ hiReportId = calcHighId(hiReportId, r.id());
+ q.prepare (m_db.m_tables["kmmReportConfig"].insertString());
+ writeReport(*it_r, q);
+ }
+ m_hiIdReports = hiReportId;
+ m_storage->loadReportId(m_hiIdReports);
+ // sqlite3 doesn't support ADD PRIMARY KEY
+ if (m_dbType == Sqlite3) {
+ if (!sqliteAlterTable(m_db.m_tables["kmmReportConfig"])) {
+ return (1);
+ }
+ } else {
+ q.prepare ("ALTER TABLE kmmReportConfig ADD PRIMARY KEY (id);");
+ if (!q.exec()) {
+ buildError (q, __func__, "Error updating kmmReportConfig pkey");
+ return (1);
+ }
+ }
+ endCommitUnit(__func__);
+ return 0;
+}
+
+/* This function attempts to cater for limitations in the sqlite ALTER TABLE
+ statement. It should enable us to drop a primary key, and drop columns */
+bool MyMoneyStorageSql::sqliteAlterTable(const MyMoneyDbTable& t) {
+ DBG("*** Entering MyMoneyStorageSql::sqliteAlterTable");
+ QString tempTableName = t.name();
+ tempTableName.replace("kmm", "tmp");
+ QSqlQuery q(this);
+ q.prepare (QString("ALTER TABLE " + t.name() + " RENAME TO " + tempTableName + ";"));
+ if (!q.exec()) {
+ buildError (q, __func__, "Error renaming table");
+ return false;
+ }
+ createTable(t);
+ q.prepare (QString("INSERT INTO " + t.name() + " (" + t.columnList() +
+ ") SELECT " + t.columnList() + " FROM " + tempTableName + ";"));
+ if (!q.exec()) {
+ buildError (q, __func__, "Error inserting into new table");
+ return false;
+ }
+ q.prepare (QString("DROP TABLE " + tempTableName + ";"));
+ if (!q.exec()) {
+ buildError (q, __func__, "Error dropping old table");
+ return false;
+ }
+ return true;
+}
+
+long unsigned MyMoneyStorageSql::getRecCount (const QString& table) const {
+ DBG("*** Entering MyMoneyStorageSql::getRecCount");
+ MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
+ q.prepare(QString("SELECT COUNT(*) FROM %1;").arg(table));
+ if ((!q.exec()) || (!q.next())) {
+ buildError (q, __func__, "error retrieving record count");
+ qFatal("Error retrieving record count"); // definitely shouldn't happen
+ }
+ return ((unsigned long) q.value(0).toULongLong());
+}
+
+int MyMoneyStorageSql::createTables () {
+ DBG("*** Entering MyMoneyStorageSql::createTables");
+ // check tables, create if required
+ // convert everything to lower case, since SQL standard is case insensitive
+ // table and column names (when not delimited), but some DBMSs disagree.
+ QStringList lowerTables = tables(QSql::AllTables);
+ for (QStringList::iterator i = lowerTables.begin(); i != lowerTables.end(); ++i) {
+ (*i) = (*i).lower();
+ }
+
+ for (QMapConstIterator<QString, MyMoneyDbTable> tt = m_db.tableBegin(); tt != m_db.tableEnd(); ++tt) {
+ if (!lowerTables.contains(tt.key().lower())) createTable (tt.data());
+ }
+
+ MyMoneySqlQuery q(this);
+ for (QMapConstIterator<QString, MyMoneyDbView> tt = m_db.viewBegin(); tt != m_db.viewEnd(); ++tt) {
+ if (!lowerTables.contains(tt.key().lower())) {
+ q.prepare (tt.data().createString());
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString ("creating view %1").arg(tt.key())));
+ }
+ }
+
+ // get the current db version from kmmFileInfo.
+ // upgrade if necessary.
+
+ return (upgradeDb()); // any errors will be caught by exception handling
+}
+
+void MyMoneyStorageSql::createTable (const MyMoneyDbTable& t) {
+ DBG("*** Entering MyMoneyStorageSql::createTable");
+// create the tables
+ QStringList ql = QStringList::split('\n', t.generateCreateSQL(m_dbType));
+ MyMoneySqlQuery q(this);
+ for (unsigned int i = 0; i < ql.count(); ++i) {
+ q.prepare (ql[i]);
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString ("creating table/index %1").arg(t.name())));
+ }
+}
+
+int MyMoneyStorageSql::isEmpty () {
+ DBG("*** Entering MyMoneyStorageSql::isEmpty");
+ // check all tables are empty
+ QMapConstIterator<QString, MyMoneyDbTable> tt = m_db.tableBegin();
+ int recordCount = 0;
+ MyMoneySqlQuery q(this);
+ while ((tt != m_db.tableEnd()) && (recordCount == 0)) {
+ q.prepare (QString("select count(*) from %1;").arg((*tt).name()));
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "getting record count"));
+ if (!q.next()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "retrieving record count"));
+ recordCount += q.value(0).toInt();
+ ++tt;
+ }
+
+ if (recordCount != 0) {
+ return (-1); // not empty
+ } else {
+ return (0);
+ }
+}
+
+void MyMoneyStorageSql::clean() {
+ DBG("*** Entering MyMoneyStorageSql::clean");
+// delete all existing records
+ QMapConstIterator<QString, MyMoneyDbTable> it = m_db.tableBegin();
+ MyMoneySqlQuery q(this);
+ while (it != m_db.tableEnd()) {
+ q.prepare(QString("DELETE from %1;").arg(it.key()));
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString ("cleaning database")));
+ ++it;
+ }
+}
+
+//////////////////////////////////////////////////////////////////
+
+bool MyMoneyStorageSql::readFile(void) {
+ DBG("*** Entering MyMoneyStorageSql::readFile");
+ m_displayStatus = true;
+ try {
+ readFileInfo();
+ readInstitutions();
+ if (m_loadAll) {
+ readPayees();
+ } else {
+ QValueList<QString> user;
+ user.append(QString("USER"));
+ readPayees(user);
+ }
+ //TRACE("done payees");
+ readCurrencies();
+ //TRACE("done currencies");
+ readSecurities();
+ //TRACE("done securities");
+ readAccounts();
+ if (m_loadAll) {
+ readTransactions();
+ } else {
+ if (m_preferred.filterSet().singleFilter.accountFilter) readTransactions (m_preferred);
+ }
+ //TRACE("done accounts");
+ readSchedules();
+ //TRACE("done schedules");
+ readPrices();
+ //TRACE("done prices");
+ readReports();
+ //TRACE("done reports");
+ readBudgets();
+ //TRACE("done budgets");
+ //FIXME - ?? if (m_mode == 0)
+ //m_storage->rebuildAccountBalances();
+ // this seems to be nonsense, but it clears the dirty flag
+ // as a side-effect.
+ m_storage->setLastModificationDate(m_storage->lastModificationDate());
+ // FIXME?? if (m_mode == 0) m_storage = NULL;
+ // make sure the progress bar is not shown any longer
+ signalProgress(-1, -1);
+ m_displayStatus = false;
+ //MyMoneySqlQuery::traceOn();
+ return true;
+ } catch (QString& s) {
+ return false;
+ }
+}
+
+// The following is called from 'SaveAsDatabase'
+bool MyMoneyStorageSql::writeFile(void) {
+ DBG("*** Entering MyMoneyStorageSql::writeFile");
+ // initialize record counts and hi ids
+ m_institutions = m_accounts = m_payees = m_transactions = m_splits
+ = m_securities = m_prices = m_currencies = m_schedules = m_reports = m_kvps = m_budgets = 0;
+ m_hiIdInstitutions = m_hiIdPayees = m_hiIdAccounts = m_hiIdTransactions =
+ m_hiIdSchedules = m_hiIdSecurities = m_hiIdReports = m_hiIdBudgets = 0;
+ m_displayStatus = true;
+ try{
+ startCommitUnit(__func__);
+ writeInstitutions ();
+ writePayees();
+ writeAccounts();
+ writeTransactions();
+ writeSchedules();
+ writeSecurities();
+ writePrices();
+ writeCurrencies();
+ writeReports();
+ writeBudgets();
+ writeFileInfo();
+ // this seems to be nonsense, but it clears the dirty flag
+ // as a side-effect.
+ //m_storage->setLastModificationDate(m_storage->lastModificationDate());
+ // FIXME?? if (m_mode == 0) m_storage = NULL;
+ endCommitUnit(__func__);
+ // make sure the progress bar is not shown any longer
+ signalProgress(-1, -1);
+ m_displayStatus = false;
+ return true;
+} catch (QString& s) {
+ return false;
+}
+}
+// --------------- SQL Transaction (commit unit) handling -----------------------------------
+void MyMoneyStorageSql::startCommitUnit (const QString& callingFunction) {
+ DBG("*** Entering MyMoneyStorageSql::startCommitUnit");
+ if (m_commitUnitStack.isEmpty()) {
+ if (!transaction()) throw new MYMONEYEXCEPTION(buildError (MyMoneySqlQuery(), __func__, "starting commit unit"));
+ }
+ m_commitUnitStack.push(callingFunction);
+}
+
+bool MyMoneyStorageSql::endCommitUnit (const QString& callingFunction) {
+ DBG("*** Entering MyMoneyStorageSql::endCommitUnit");
+ // for now, we don't know if there were any changes made to the data so
+ // we expect the data to have changed. This assumption causes some unnecessary
+ // repaints of the UI here and there, but for now it's ok. If we can determine
+ // that the commit() really changes the data, we can return that information
+ // as value of this method.
+ bool rc = true;
+ if (callingFunction != m_commitUnitStack.top())
+ qDebug("%s", QString("%1 - %2 s/be %3").arg(__func__).arg(callingFunction).arg(m_commitUnitStack.top()).data());
+ m_commitUnitStack.pop();
+ if (m_commitUnitStack.isEmpty()) {
+ if (!commit()) throw new MYMONEYEXCEPTION(buildError (MyMoneySqlQuery(), __func__, "ending commit unit"));
+ }
+ return rc;
+}
+
+void MyMoneyStorageSql::cancelCommitUnit (const QString& callingFunction) {
+ DBG("*** Entering MyMoneyStorageSql::cancelCommitUnit");
+ if (callingFunction != m_commitUnitStack.top())
+ qDebug("%s", QString("%1 - %2 s/be %3").arg(__func__).arg(callingFunction).arg(m_commitUnitStack.top()).data());
+ if (m_commitUnitStack.isEmpty()) return;
+ m_commitUnitStack.clear();
+ if (!rollback()) throw new MYMONEYEXCEPTION(buildError (MyMoneySqlQuery(), __func__, "cancelling commit unit"));
+}
+
+/////////////////////////////////////////////////////////////////////
+void MyMoneyStorageSql::fillStorage() {
+ DBG("*** Entering MyMoneyStorageSql::fillStorage");
+// if (!m_transactionListRead) // make sure we have loaded everything
+ readTransactions();
+// if (!m_payeeListRead)
+ readPayees();
+}
+
+//------------------------------ Write SQL routines ----------------------------------------
+// **** Institutions ****
+void MyMoneyStorageSql::writeInstitutions() {
+ DBG("*** Entering MyMoneyStorageSql::writeInstitutions");
+ // first, get a list of what's on the database
+ // anything not in the list needs to be inserted
+ // anything which is will be updated and removed from the list
+ // anything left over at the end will need to be deleted
+ // this is an expensive and inconvenient way to do things; find a better way
+ // one way would be to build the lists when reading the db
+ // unfortunately this object does not persist between read and write
+ // it would also be nice if we could tell which objects had been updated since we read them in
+ QValueList<QString> dbList;
+ MyMoneySqlQuery q(this);
+ q.prepare("SELECT id FROM kmmInstitutions;");
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "building Institution list"));
+ while (q.next()) dbList.append(q.value(0).toString());
+
+ const QValueList<MyMoneyInstitution> list = m_storage->institutionList();
+ QValueList<MyMoneyInstitution>::ConstIterator it;
+ MyMoneySqlQuery q2(this);
+ q.prepare (m_db.m_tables["kmmInstitutions"].updateString());
+ q2.prepare (m_db.m_tables["kmmInstitutions"].insertString());
+ signalProgress(0, list.count(), "Writing Institutions...");
+ for(it = list.begin(); it != list.end(); ++it) {
+ if (dbList.contains((*it).id())) {
+ dbList.remove ((*it).id());
+ writeInstitution(*it, q);
+ } else {
+ writeInstitution(*it, q2);
+ }
+ signalProgress (++m_institutions, 0);
+ }
+
+ if (!dbList.isEmpty()) {
+ QValueList<QString>::const_iterator it = dbList.begin();
+ q.prepare("DELETE FROM kmmInstitutions WHERE id = :id");
+ while (it != dbList.end()) {
+ q.bindValue(":id", (*it));
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Institution"));
+ deleteKeyValuePairs("OFXSETTINGS", (*it));
+ ++it;
+ }
+ }
+}
+
+void MyMoneyStorageSql::addInstitution(const MyMoneyInstitution& inst) {
+ DBG("*** Entering MyMoneyStorageSql::addInstitution");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmInstitutions"].insertString());
+ writeInstitution(inst ,q);
+ ++m_institutions;
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::modifyInstitution(const MyMoneyInstitution& inst) {
+ DBG("*** Entering MyMoneyStorageSql::modifyInstitution");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmInstitutions"].updateString());
+ deleteKeyValuePairs("OFXSETTINGS", inst.id());
+ writeInstitution(inst ,q);
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::removeInstitution(const MyMoneyInstitution& inst) {
+ DBG("*** Entering MyMoneyStorageSql::removeInstitution");
+ startCommitUnit(__func__);
+ deleteKeyValuePairs("OFXSETTINGS", inst.id());
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmInstitutions"].deleteString());
+ q.bindValue(":id", inst.id());
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("deleting Institution")));
+ --m_institutions;
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::writeInstitution(const MyMoneyInstitution& i, MyMoneySqlQuery& q) {
+ DBG("*** Entering MyMoneyStorageSql::writeInstitution");
+ q.bindValue(":id", i.id());
+ q.bindValue(":name", i.name());
+ q.bindValue(":manager", i.manager());
+ q.bindValue(":routingCode", i.sortcode());
+ q.bindValue(":addressStreet", i.street());
+ q.bindValue(":addressCity", i.city());
+ q.bindValue(":addressZipcode", i.postcode());
+ q.bindValue(":telephone", i.telephone());
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("writing Institution")));
+ writeKeyValuePairs("OFXSETTINGS", i.id(), i.pairs());
+ m_hiIdInstitutions = calcHighId(m_hiIdInstitutions, i.id());
+}
+
+// **** Payees ****
+void MyMoneyStorageSql::writePayees() {
+ DBG("*** Entering MyMoneyStorageSql::writePayees");
+ // first, get a list of what's on the database (see writeInstitutions)
+ QValueList<QString> dbList;
+ MyMoneySqlQuery q(this);
+ q.prepare("SELECT id FROM kmmPayees;");
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "building Payee list"));
+ while (q.next()) dbList.append(q.value(0).toString());
+
+ QValueList<MyMoneyPayee> list = m_storage->payeeList();
+ MyMoneyPayee user(QString("USER"), m_storage->user());
+ list.prepend(user);
+ signalProgress(0, list.count(), "Writing Payees...");
+ MyMoneySqlQuery q2(this);
+ q.prepare (m_db.m_tables["kmmPayees"].updateString());
+ q2.prepare (m_db.m_tables["kmmPayees"].insertString());
+ QValueList<MyMoneyPayee>::ConstIterator it;
+ for(it = list.begin(); it != list.end(); ++it) {
+ if (dbList.contains((*it).id())) {
+ dbList.remove ((*it).id());
+ writePayee(*it, q);
+ } else {
+ writePayee(*it, q2);
+ }
+ signalProgress(++m_payees, 0);
+ }
+
+ if (!dbList.isEmpty()) {
+ QValueList<QString>::const_iterator it = dbList.begin();
+ q.prepare(m_db.m_tables["kmmPayees"].deleteString());
+ while (it != dbList.end()) {
+ q.bindValue(":id", (*it));
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Payee"));
+ m_payees -= q.numRowsAffected();
+ ++it;
+ }
+ }
+}
+
+void MyMoneyStorageSql::addPayee(const MyMoneyPayee& payee) {
+ DBG("*** Entering MyMoneyStorageSql::addPayee");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmPayees"].insertString());
+ writePayee(payee,q);
+ ++m_payees;
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::modifyPayee(const MyMoneyPayee& payee) {
+ DBG("*** Entering MyMoneyStorageSql::modifyPayee");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmPayees"].updateString());
+ writePayee(payee,q);
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::modifyUserInfo(const MyMoneyPayee& payee) {
+ DBG("*** Entering MyMoneyStorageSql::modifyUserInfo");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmPayees"].updateString());
+ writePayee(payee,q, true);
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::removePayee(const MyMoneyPayee& payee) {
+ DBG("*** Entering MyMoneyStorageSql::removePayee");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmPayees"].deleteString());
+ q.bindValue(":id", payee.id());
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("deleting Payee")));
+ --m_payees;
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::writePayee(const MyMoneyPayee& p, MyMoneySqlQuery& q, bool isUserInfo) {
+ DBG("*** Entering MyMoneyStorageSql::writePayee");
+ if (isUserInfo) {
+ q.bindValue(":id", "USER");
+ } else {
+ q.bindValue(":id", p.id());
+ }
+ q.bindValue(":name", p.name());
+ q.bindValue(":reference", p.reference());
+ q.bindValue(":email", p.email());
+ q.bindValue(":addressStreet", p.address());
+ q.bindValue(":addressCity", p.city());
+ q.bindValue(":addressZipcode", p.postcode());
+ q.bindValue(":addressState", p.state());
+ q.bindValue(":telephone", p.telephone());
+ q.bindValue(":notes", p.notes());
+ q.bindValue(":defaultAccountId", p.defaultAccountId());
+ bool ignoreCase;
+ QString matchKeys;
+ MyMoneyPayee::payeeMatchType type = p.matchData(ignoreCase, matchKeys);
+ q.bindValue(":matchData", static_cast<unsigned int>(type));
+ if (ignoreCase) q.bindValue(":matchIgnoreCase", "Y");
+ else q.bindValue(":matchIgnoreCase", "N");
+ q.bindValue(":matchKeys", matchKeys);
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString ("writing Payee")));
+ if (!isUserInfo) m_hiIdPayees = calcHighId(m_hiIdPayees, p.id());
+}
+
+// **** Accounts ****
+void MyMoneyStorageSql::writeAccounts() {
+ DBG("*** Entering MyMoneyStorageSql::writeAccounts");
+ // first, get a list of what's on the database (see writeInstitutions)
+ QValueList<QString> dbList;
+ MyMoneySqlQuery q(this);
+ q.prepare("SELECT id FROM kmmAccounts;");
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "building Account list"));
+ while (q.next()) dbList.append(q.value(0).toString());
+
+ QValueList<MyMoneyAccount> list;
+ m_storage->accountList(list);
+ QValueList<MyMoneyAccount>::ConstIterator it;
+ signalProgress(0, list.count(), "Writing Accounts...");
+ if (dbList.isEmpty()) { // new table, insert standard accounts
+ q.prepare (m_db.m_tables["kmmAccounts"].insertString());
+ } else {
+ q.prepare (m_db.m_tables["kmmAccounts"].updateString());
+ }
+ // Attempt to write the standard accounts. For an empty db, this will fail.
+ TRY
+ writeAccount(m_storage->asset(), q); ++m_accounts;
+ writeAccount(m_storage->liability(), q); ++m_accounts;
+ writeAccount(m_storage->expense(), q); ++m_accounts;
+ writeAccount(m_storage->income(), q); ++m_accounts;
+ writeAccount(m_storage->equity(), q); ++m_accounts;
+ CATCH
+ delete e;
+
+ // If the above failed, assume that the database is empty and create
+ // the standard accounts by hand before writing them.
+ MyMoneyAccount acc_l;
+ acc_l.setAccountType(MyMoneyAccount::Liability);
+ acc_l.setName("Liability");
+ MyMoneyAccount liability(STD_ACC_LIABILITY, acc_l);
+
+ MyMoneyAccount acc_a;
+ acc_a.setAccountType(MyMoneyAccount::Asset);
+ acc_a.setName("Asset");
+ MyMoneyAccount asset(STD_ACC_ASSET, acc_a);
+
+ MyMoneyAccount acc_e;
+ acc_e.setAccountType(MyMoneyAccount::Expense);
+ acc_e.setName("Expense");
+ MyMoneyAccount expense(STD_ACC_EXPENSE, acc_e);
+
+ MyMoneyAccount acc_i;
+ acc_i.setAccountType(MyMoneyAccount::Income);
+ acc_i.setName("Income");
+ MyMoneyAccount income(STD_ACC_INCOME, acc_i);
+
+ MyMoneyAccount acc_q;
+ acc_q.setAccountType(MyMoneyAccount::Equity);
+ acc_q.setName("Equity");
+ MyMoneyAccount equity(STD_ACC_EQUITY, acc_q);
+
+ writeAccount(asset, q); ++m_accounts;
+ writeAccount(expense, q); ++m_accounts;
+ writeAccount(income, q); ++m_accounts;
+ writeAccount(liability, q); ++m_accounts;
+ writeAccount(equity, q); ++m_accounts;
+ ECATCH
+
+ int i = 0;
+ MyMoneySqlQuery q2(this);
+ q.prepare (m_db.m_tables["kmmAccounts"].updateString());
+ q2.prepare (m_db.m_tables["kmmAccounts"].insertString());
+ // Update the accounts that exist; insert the ones that do not.
+ for(it = list.begin(); it != list.end(); ++it, ++i) {
+ m_transactionCountMap[(*it).id()] = m_storagePtr->transactionCount((*it).id());
+ if (dbList.contains((*it).id())) {
+ dbList.remove ((*it).id());
+ writeAccount(*it, q);
+ } else {
+ writeAccount(*it, q2);
+ }
+ signalProgress(++m_accounts, 0);
+ }
+
+ // Delete the accounts that are in the db but no longer in memory.
+ if (!dbList.isEmpty()) {
+ QValueList<QString>::const_iterator it = dbList.begin();
+ q.prepare("DELETE FROM kmmAccounts WHERE id = :id");
+ while (it != dbList.end()) {
+ if (!m_storagePtr->isStandardAccount(*it)) {
+ q.bindValue(":id", (*it));
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Account"));
+ deleteKeyValuePairs("ACCOUNT", (*it));
+ deleteKeyValuePairs("ONLINEBANKING", (*it));
+ }
+ ++it;
+ }
+ }
+}
+
+void MyMoneyStorageSql::addAccount(const MyMoneyAccount& acc) {
+ DBG("*** Entering MyMoneyStorageSql::addAccount");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmAccounts"].insertString());
+ writeAccount(acc,q);
+ ++m_accounts;
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::modifyAccount(const MyMoneyAccount& acc) {
+ DBG("*** Entering MyMoneyStorageSql::modifyAccount");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmAccounts"].updateString());
+ deleteKeyValuePairs("ACCOUNT", acc.id());
+ deleteKeyValuePairs("ONLINEBANKING", acc.id());
+ writeAccount(acc,q);
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::removeAccount(const MyMoneyAccount& acc) {
+ DBG("*** Entering MyMoneyStorageSql::removeAccount");
+ startCommitUnit(__func__);
+ deleteKeyValuePairs("ACCOUNT", acc.id());
+ deleteKeyValuePairs("ONLINEBANKING", acc.id());
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmAccounts"].deleteString());
+ q.bindValue(":id", acc.id());
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("deleting Account")));
+ --m_accounts;
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::writeAccount(const MyMoneyAccount& acc, MyMoneySqlQuery& q) {
+ DBG("*** Entering MyMoneyStorageSql::writeAccount");
+ //MyMoneyMoney balance = m_storagePtr->balance(acc.id(), QDate());
+ q.bindValue(":id", acc.id());
+ q.bindValue(":institutionId", acc.institutionId());
+ q.bindValue(":parentId", acc.parentAccountId());
+ if (acc.lastReconciliationDate() == QDate())
+ q.bindValue(":lastReconciled", acc.lastReconciliationDate());
+ else
+ q.bindValue(":lastReconciled", acc.lastReconciliationDate().toString(Qt::ISODate));
+
+ q.bindValue(":lastModified", acc.lastModified());
+ if (acc.openingDate() == QDate())
+ q.bindValue(":openingDate", acc.openingDate());
+ else
+ q.bindValue(":openingDate", acc.openingDate().toString(Qt::ISODate));
+
+ q.bindValue(":accountNumber", acc.number());
+ q.bindValue(":accountType", acc.accountType());
+ q.bindValue(":accountTypeString", MyMoneyAccount::accountTypeToString(acc.accountType()));
+ if (acc.accountType() == MyMoneyAccount::Stock) {
+ q.bindValue(":isStockAccount", "Y");
+ } else {
+ q.bindValue(":isStockAccount", "N");
+ }
+ q.bindValue(":accountName", acc.name());
+ q.bindValue(":description", acc.description());
+ q.bindValue(":currencyId", acc.currencyId());
+
+ // This section attempts to get the balance from the database, if possible
+ // That way, the balance fields are kept in sync. If that fails, then
+ // It is assumed that the account actually knows its correct balance.
+
+ //FIXME: Using exceptions for branching always feels like a kludge.
+ // Look for a better way.
+ TRY
+ MyMoneyMoney bal = m_storagePtr->balance(acc.id(), QDate());
+ q.bindValue(":balance", bal.toString());
+ q.bindValue(":balanceFormatted",
+ bal.formatMoney("", -1, false));
+ CATCH
+ delete e;
+ q.bindValue(":balance", acc.balance().toString());
+ q.bindValue(":balanceFormatted",
+ acc.balance().formatMoney("", -1, false));
+ ECATCH
+
+ q.bindValue(":transactionCount", Q_ULLONG(m_transactionCountMap[acc.id()]));
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("writing Account")));
+
+ //Add in Key-Value Pairs for accounts.
+ //MMAccount inherits from KVPContainer AND has a KVPContainer member
+ //so handle both
+ writeKeyValuePairs("ACCOUNT", acc.id(), acc.pairs());
+ writeKeyValuePairs("ONLINEBANKING", acc.id(), acc.onlineBankingSettings().pairs());
+ m_hiIdAccounts = calcHighId(m_hiIdAccounts, acc.id());
+}
+
+// **** Transactions and Splits ****
+void MyMoneyStorageSql::writeTransactions() {
+ DBG("*** Entering MyMoneyStorageSql::writeTransactions");
+ // first, get a list of what's on the database (see writeInstitutions)
+ QValueList<QString> dbList;
+ MyMoneySqlQuery q(this);
+ q.prepare("SELECT id FROM kmmTransactions WHERE txType = 'N';");
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "building Transaction list"));
+ while (q.next()) dbList.append(q.value(0).toString());
+
+ MyMoneyTransactionFilter filter;
+ filter.setReportAllSplits(false);
+ QValueList<MyMoneyTransaction> list;
+ m_storage->transactionList(list, filter);
+ signalProgress(0, list.count(), "Writing Transactions...");
+ QValueList<MyMoneyTransaction>::ConstIterator it;
+ int i = 0;
+ MyMoneySqlQuery q2(this);
+ q.prepare (m_db.m_tables["kmmTransactions"].updateString());
+ q2.prepare (m_db.m_tables["kmmTransactions"].insertString());
+ for(it = list.begin(); it != list.end(); ++it, ++i) {
+ if (dbList.contains((*it).id())) {
+ dbList.remove ((*it).id());
+ writeTransaction((*it).id(), *it, q, "N");
+ } else {
+ writeTransaction((*it).id(), *it, q2, "N");
+ }
+ signalProgress(++m_transactions, 0);
+ }
+
+ if (!dbList.isEmpty()) {
+ QValueList<QString>::const_iterator it = dbList.begin();
+ while (it != dbList.end()) {
+ deleteTransaction(*it);
+ ++it;
+ }
+ }
+}
+
+void MyMoneyStorageSql::addTransaction (const MyMoneyTransaction& tx) {
+ DBG("*** Entering MyMoneyStorageSql::addTransaction");
+ startCommitUnit(__func__);
+ // add the transaction and splits
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmTransactions"].insertString());
+ writeTransaction(tx.id(), tx, q, "N");
+ ++m_transactions;
+ // for each split account, update lastMod date, balance, txCount
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+ for(it_s = tx.splits().begin(); it_s != tx.splits().end(); ++it_s) {
+ //MyMoneyAccount acc = m_storagePtr->account((*it_s).accountId());
+ MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId());
+ ++m_transactionCountMap[acc.id()];
+ modifyAccount(acc);
+ }
+ // in the fileinfo record, update lastMod, txCount, next TxId
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::modifyTransaction (const MyMoneyTransaction& tx) {
+ DBG("*** Entering MyMoneyStorageSql::modifyTransaction");
+ startCommitUnit(__func__);
+ // remove the splits of the old tx from the count table
+ MyMoneySqlQuery q(this);
+ q.prepare ("SELECT accountId FROM kmmSplits WHERE transactionId = :txId;");
+ q.bindValue(":txId", tx.id());
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "retrieving old splits"));
+ while (q.next()) {
+ QString id = q.value(0).toCString();
+ --m_transactionCountMap[id];
+ }
+ // add the transaction and splits
+ q.prepare (m_db.m_tables["kmmTransactions"].updateString());
+ writeTransaction(tx.id(), tx, q, "N");
+ // for each split account, update lastMod date, balance, txCount
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+ for(it_s = tx.splits().begin(); it_s != tx.splits().end(); ++it_s) {
+ //MyMoneyAccount acc = m_storagePtr->account((*it_s).accountId());
+ MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId());
+ ++m_transactionCountMap[acc.id()];
+ modifyAccount(acc);
+ }
+ writeSplits(tx.id(), "N", tx.splits());
+ // in the fileinfo record, update lastMod
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::removeTransaction(const MyMoneyTransaction& tx) {
+ DBG("*** Entering MyMoneyStorageSql::removeTransaction");
+ startCommitUnit(__func__);
+ deleteTransaction(tx.id());
+ --m_transactions;
+
+ // for each split account, update lastMod date, balance, txCount
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+ for(it_s = tx.splits().begin(); it_s != tx.splits().end(); ++it_s) {
+ MyMoneyAccount acc = m_storagePtr->account((*it_s).accountId());
+ --m_transactionCountMap[acc.id()];
+ modifyAccount(acc);
+ }
+ // in the fileinfo record, update lastModDate, txCount
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::deleteTransaction(const QString& id) {
+ DBG("*** Entering MyMoneyStorageSql::deleteTransaction");
+ MyMoneySqlQuery q(this);
+ q.prepare("DELETE FROM kmmSplits WHERE transactionId = :transactionId;");
+ q.bindValue(":transactionId", id);
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Splits"));
+
+ q.prepare ("DELETE FROM kmmKeyValuePairs WHERE kvpType = 'SPLIT' "
+ "AND kvpId LIKE '" + id + "%'");
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Splits KVP"));
+
+ m_splits -= q.numRowsAffected();
+ deleteKeyValuePairs("TRANSACTION", id);
+ q.prepare(m_db.m_tables["kmmTransactions"].deleteString());
+ q.bindValue(":id", id);
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Transaction"));
+}
+
+void MyMoneyStorageSql::writeTransaction(const QString& txId, const MyMoneyTransaction& tx, MyMoneySqlQuery& q, const QString& type) {
+ DBG("*** Entering MyMoneyStorageSql::writeTransaction");
+ q.bindValue(":id", txId);
+ q.bindValue(":txType", type);
+ q.bindValue(":postDate", tx.postDate().toString(Qt::ISODate));
+ q.bindValue(":memo", tx.memo());
+ q.bindValue(":entryDate", tx.entryDate().toString(Qt::ISODate));
+ q.bindValue(":currencyId", tx.commodity());
+ q.bindValue(":bankId", tx.bankID());
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("writing Transaction")));
+
+ m_txPostDate = tx.postDate(); // FIXME: TEMP till Tom puts date in split object
+ QValueList<MyMoneySplit> splitList = tx.splits();
+ writeSplits(txId, type, splitList);
+
+ //Add in Key-Value Pairs for transactions.
+ deleteKeyValuePairs("TRANSACTION", txId);
+ writeKeyValuePairs("TRANSACTION", txId, tx.pairs());
+ m_hiIdTransactions = calcHighId(m_hiIdTransactions, tx.id());
+}
+
+void MyMoneyStorageSql::writeSplits(const QString& txId, const QString& type, const QValueList<MyMoneySplit>& splitList) {
+ DBG("*** Entering MyMoneyStorageSql::writeSplits");
+ // first, get a list of what's on the database (see writeInstitutions)
+ QValueList<unsigned int> dbList;
+ MyMoneySqlQuery q(this);
+ q.prepare("SELECT splitId FROM kmmSplits where transactionId = :id;");
+ q.bindValue(":id", txId);
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "building Split list"));
+ while (q.next()) dbList.append(q.value(0).toUInt());
+
+ QValueList<MyMoneySplit>::const_iterator it;
+ unsigned int i;
+ MyMoneySqlQuery q2(this);
+ q.prepare (m_db.m_tables["kmmSplits"].updateString());
+ q2.prepare (m_db.m_tables["kmmSplits"].insertString());
+ for(it = splitList.begin(), i = 0; it != splitList.end(); ++it, ++i) {
+ if (dbList.contains(i)) {
+ dbList.remove (i);
+ writeSplit(txId, (*it), type, i, q);
+ } else {
+ ++m_splits;
+ writeSplit(txId, (*it), type, i, q2);
+ }
+ }
+
+ if (!dbList.isEmpty()) {
+ q.prepare("DELETE FROM kmmSplits WHERE transactionId = :txId AND splitId = :splitId");
+ QValueList<unsigned int>::const_iterator it = dbList.begin();
+ while (it != dbList.end()) {
+ q.bindValue(":txId", txId);
+ q.bindValue(":splitId", *it);
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Splits"));
+ ++it;
+ }
+ }
+}
+
+void MyMoneyStorageSql::writeSplit(const QString& txId, const MyMoneySplit& split,
+ const QString& type, const int splitId, MyMoneySqlQuery& q) {
+ DBG("*** Entering MyMoneyStorageSql::writeSplit");
+ q.bindValue(":transactionId", txId);
+ q.bindValue(":txType", type);
+ q.bindValue(":splitId", splitId);
+ q.bindValue(":payeeId", split.payeeId());
+ if (split.reconcileDate() == QDate())
+ q.bindValue(":reconcileDate", split.reconcileDate());
+ else
+ q.bindValue(":reconcileDate", split.reconcileDate().toString(Qt::ISODate));
+ q.bindValue(":action", split.action());
+ q.bindValue(":reconcileFlag", split.reconcileFlag());
+ q.bindValue(":value", split.value().toString());
+ q.bindValue(":valueFormatted", split.value()
+ .formatMoney("", -1, false)
+ .replace(QChar(','), QChar('.')));
+ q.bindValue(":shares", split.shares().toString());
+ MyMoneyAccount acc = m_storagePtr->account(split.accountId());
+ MyMoneySecurity sec = m_storagePtr->security(acc.currencyId());
+ q.bindValue(":sharesFormatted",
+ split.shares().
+ formatMoney("", MyMoneyMoney::denomToPrec(sec.smallestAccountFraction()), false).
+ replace(QChar(','), QChar('.')));
+ MyMoneyMoney price = split.actualPrice();
+ if (!price.isZero()) {
+ q.bindValue(":price", price.toString());
+ q.bindValue(":priceFormatted", price.formatMoney
+ ("", KMyMoneySettings::pricePrecision(), false)
+ .replace(QChar(','), QChar('.')));
+ } else {
+ q.bindValue(":price", QString());
+ q.bindValue(":priceFormatted", QString());
+ }
+ q.bindValue(":memo", split.memo());
+ q.bindValue(":accountId", split.accountId());
+ q.bindValue(":checkNumber", split.number());
+ q.bindValue(":postDate", m_txPostDate.toString(Qt::ISODate)); // FIXME: when Tom puts date into split object
+ q.bindValue(":bankId", split.bankID());
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("writing Split")));
+ deleteKeyValuePairs("SPLIT", txId + QString::number(splitId));
+ writeKeyValuePairs("SPLIT", txId + QString::number(splitId), split.pairs());
+}
+
+// **** Schedules ****
+void MyMoneyStorageSql::writeSchedules() {
+ DBG("*** Entering MyMoneyStorageSql::writeSchedules");
+ // first, get a list of what's on the database (see writeInstitutions)
+ QValueList<QString> dbList;
+ MyMoneySqlQuery q(this);
+ q.prepare("SELECT id FROM kmmSchedules;");
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "building Schedule list"));
+ while (q.next()) dbList.append(q.value(0).toString());
+
+ const QValueList<MyMoneySchedule> list = m_storage->scheduleList();
+ QValueList<MyMoneySchedule>::ConstIterator it;
+ MyMoneySqlQuery q2(this);
+ //TODO: find a way to prepare the queries outside of the loop. writeSchedule()
+ // modifies the query passed to it, so they have to be re-prepared every pass.
+ signalProgress(0, list.count(), "Writing Schedules...");
+ for(it = list.begin(); it != list.end(); ++it) {
+ q.prepare (m_db.m_tables["kmmSchedules"].updateString());
+ q2.prepare (m_db.m_tables["kmmSchedules"].insertString());
+ bool insert = true;
+ if (dbList.contains((*it).id())) {
+ dbList.remove ((*it).id());
+ insert = false;
+ writeSchedule(*it, q, insert);
+ } else {
+ writeSchedule(*it, q2, insert);
+ }
+ signalProgress(++m_schedules, 0);
+ }
+
+ if (!dbList.isEmpty()) {
+ QValueList<QString>::const_iterator it = dbList.begin();
+ while (it != dbList.end()) {
+ deleteSchedule(*it);
+ ++it;
+ }
+ }
+}
+
+void MyMoneyStorageSql::addSchedule(const MyMoneySchedule& sched) {
+ DBG("*** Entering MyMoneyStorageSql::addSchedule");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmSchedules"].insertString());
+ writeSchedule(sched,q, true);
+ ++m_schedules;
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::modifySchedule(const MyMoneySchedule& sched) {
+ DBG("*** Entering MyMoneyStorageSql::modifySchedule");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmSchedules"].updateString());
+ writeSchedule(sched,q, false);
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::removeSchedule(const MyMoneySchedule& sched) {
+ DBG("*** Entering MyMoneyStorageSql::removeSchedule");
+ startCommitUnit(__func__);
+ deleteSchedule(sched.id());
+ --m_schedules;
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::deleteSchedule (const QString& id) {
+ DBG("*** Entering MyMoneyStorageSql::deleteSchedule");
+ deleteTransaction(id);
+ MyMoneySqlQuery q(this);
+ q.prepare("DELETE FROM kmmSchedulePaymentHistory WHERE schedId = :id");
+ q.bindValue(":id", id);
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Schedule Payment History"));
+ q.prepare(m_db.m_tables["kmmSchedules"].deleteString());
+ q.bindValue(":id", id);
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Schedule"));
+ //FIXME: enable when schedules have KVPs.
+ //deleteKeyValuePairs("SCHEDULE", id);
+}
+
+void MyMoneyStorageSql::writeSchedule(const MyMoneySchedule& sch, MyMoneySqlQuery& q, bool insert) {
+ DBG("*** Entering MyMoneyStorageSql::writeSchedule");
+ q.bindValue(":id", sch.id());
+ q.bindValue(":name", sch.name());
+ q.bindValue(":type", sch.type());
+ q.bindValue(":typeString", MyMoneySchedule::scheduleTypeToString(sch.type()));
+ q.bindValue(":occurence", sch.occurencePeriod());
+ q.bindValue(":occurenceMultiplier", sch.occurenceMultiplier());
+ q.bindValue(":occurenceString", sch.occurenceToString());
+ q.bindValue(":paymentType", sch.paymentType());
+ q.bindValue(":paymentTypeString", MyMoneySchedule::paymentMethodToString(sch.paymentType()));
+ q.bindValue(":startDate", sch.startDate().toString(Qt::ISODate));
+ q.bindValue(":endDate", sch.endDate().toString(Qt::ISODate));
+ if (sch.isFixed()) {
+ q.bindValue(":fixed", "Y");
+ } else {
+ q.bindValue(":fixed", "N");
+ }
+ if (sch.autoEnter()) {
+ q.bindValue(":autoEnter", "Y");
+ } else {
+ q.bindValue(":autoEnter", "N");
+ }
+ q.bindValue(":lastPayment", sch.lastPayment());
+ q.bindValue(":nextPaymentDue", sch.nextDueDate().toString(Qt::ISODate));
+ q.bindValue(":weekendOption", sch.weekendOption());
+ q.bindValue(":weekendOptionString", MyMoneySchedule::weekendOptionToString(sch.weekendOption()));
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("writing Schedules")));
+
+ //store the payment history for this scheduled task.
+ //easiest way is to delete all and re-insert; it's not a high use table
+ q.prepare("DELETE FROM kmmSchedulePaymentHistory WHERE schedId = :id;");
+ q.bindValue(":id", sch.id());
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("deleting Schedule Payment History")));
+
+ q.prepare (m_db.m_tables["kmmSchedulePaymentHistory"].insertString());
+ QValueList<QDate> payments = sch.recordedPayments();
+ QValueList<QDate>::ConstIterator it;
+ for (it=payments.begin(); it!=payments.end(); ++it) {
+ q.bindValue(":schedId", sch.id());
+ q.bindValue(":payDate", (*it).toString(Qt::ISODate));
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("writing Schedule Payment History")));
+ }
+
+ //store the transaction data for this task.
+ if (!insert) {
+ q.prepare (m_db.m_tables["kmmTransactions"].updateString());
+ } else {
+ q.prepare (m_db.m_tables["kmmTransactions"].insertString());
+ }
+ writeTransaction(sch.id(), sch.transaction(), q, "S");
+
+ //FIXME: enable when schedules have KVPs.
+
+ //Add in Key-Value Pairs for transactions.
+ //deleteKeyValuePairs("SCHEDULE", sch.id());
+ //writeKeyValuePairs("SCHEDULE", sch.id(), sch.pairs());
+ m_hiIdSchedules = calcHighId(m_hiIdSchedules, sch.id());
+}
+
+// **** Securities ****
+void MyMoneyStorageSql::writeSecurities() {
+ DBG("*** Entering MyMoneyStorageSql::writeSecurities");
+ // first, get a list of what's on the database (see writeInstitutions)
+ QValueList<QString> dbList;
+ MyMoneySqlQuery q(this);
+ MyMoneySqlQuery q2(this);
+ q.prepare("SELECT id FROM kmmSecurities;");
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "building security list"));
+ while (q.next()) dbList.append(q.value(0).toString());
+
+ const QValueList<MyMoneySecurity> securityList = m_storage->securityList();
+ signalProgress(0, securityList.count(), "Writing Securities...");
+ q.prepare (m_db.m_tables["kmmSecurities"].updateString());
+ q2.prepare (m_db.m_tables["kmmSecurities"].insertString());
+ for(QValueList<MyMoneySecurity>::ConstIterator it = securityList.begin(); it != securityList.end(); ++it) {
+ if (dbList.contains((*it).id())) {
+ dbList.remove ((*it).id());
+ writeSecurity((*it), q);
+ } else {
+ writeSecurity((*it), q2);
+ }
+ signalProgress(++m_securities, 0);
+ }
+
+ if (!dbList.isEmpty()) {
+ q.prepare("DELETE FROM kmmSecurities WHERE id = :id");
+ q2.prepare("DELETE FROM kmmPrices WHERE fromId = :id OR toId = :id");
+ QValueList<QString>::const_iterator it = dbList.begin();
+ while (it != dbList.end()) {
+ q.bindValue(":id", (*it));
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Security"));
+ q2.bindValue(":fromId", (*it));
+ q2.bindValue(":toId", (*it));
+ if (!q2.exec()) throw new MYMONEYEXCEPTION(buildError (q2, __func__, "deleting Security"));
+ deleteKeyValuePairs("SECURITY", (*it));
+ ++it;
+ }
+ }
+}
+
+void MyMoneyStorageSql::addSecurity(const MyMoneySecurity& sec) {
+ DBG("*** Entering MyMoneyStorageSql::addSecurity");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmSecurities"].insertString());
+ writeSecurity(sec,q);
+ ++m_securities;
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::modifySecurity(const MyMoneySecurity& sec) {
+ DBG("*** Entering MyMoneyStorageSql::modifySecurity");
+ startCommitUnit(__func__);
+ deleteKeyValuePairs("SECURITY", sec.id());
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmSecurities"].updateString());
+ writeSecurity(sec,q);
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::removeSecurity(const MyMoneySecurity& sec) {
+ DBG("*** Entering MyMoneyStorageSql::removeSecurity");
+ startCommitUnit(__func__);
+ deleteKeyValuePairs("SECURITY", sec.id());
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmSecurities"].deleteString());
+ q.bindValue(":id", sec.id());
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("deleting Security")));
+ --m_securities;
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::writeSecurity(const MyMoneySecurity& security, MyMoneySqlQuery& q) {
+ DBG("*** Entering MyMoneyStorageSql::writeSecurity");
+ q.bindValue(":id", security.id());
+ q.bindValue(":name", security.name());
+ q.bindValue(":symbol", security.tradingSymbol());
+ q.bindValue(":type", static_cast<int>(security.securityType()));
+ q.bindValue(":typeString", MyMoneySecurity::securityTypeToString(security.securityType()));
+ q.bindValue(":smallestAccountFraction", security.smallestAccountFraction());
+ q.bindValue(":tradingCurrency", security.tradingCurrency());
+ q.bindValue(":tradingMarket", security.tradingMarket());
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString ("writing Securities")));
+
+ //Add in Key-Value Pairs for security
+ writeKeyValuePairs("SECURITY", security.id(), security.pairs());
+ m_hiIdSecurities = calcHighId(m_hiIdSecurities, security.id());
+}
+
+// **** Prices ****
+void MyMoneyStorageSql::writePrices() {
+ DBG("*** Entering MyMoneyStorageSql::writePrices");
+ // due to difficulties in matching and determining deletes
+ // easiest way is to delete all and re-insert
+ MyMoneySqlQuery q(this);
+ q.prepare("DELETE FROM kmmPrices");
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("deleting Prices")));
+ m_prices = 0;
+
+ const MyMoneyPriceList list = m_storage->priceList();
+ signalProgress(0, list.count(), "Writing Prices...");
+ MyMoneyPriceList::ConstIterator it;
+ for(it = list.begin(); it != list.end(); ++it) {
+ writePricePair(*it);
+ }
+}
+
+void MyMoneyStorageSql::writePricePair(const MyMoneyPriceEntries& p) {
+ DBG("*** Entering MyMoneyStorageSql::writePricePair");
+ MyMoneyPriceEntries::ConstIterator it;
+ for(it = p.begin(); it != p.end(); ++it) {
+ writePrice (*it);
+ signalProgress(++m_prices, 0);
+ }
+}
+
+void MyMoneyStorageSql::addPrice(const MyMoneyPrice& p) {
+ DBG("*** Entering MyMoneyStorageSql::addPrice");
+ if (m_readingPrices) return;
+ // the app always calls addPrice, whether or not there is already one there
+ startCommitUnit(__func__);
+ bool newRecord = false;
+ MyMoneySqlQuery q(this);
+ QString s = m_db.m_tables["kmmPrices"].selectAllString(false);
+ s += " WHERE fromId = :fromId AND toId = :toId AND priceDate = :priceDate;";
+ q.prepare (s);
+ q.bindValue(":fromId", p.from());
+ q.bindValue(":toId", p.to());
+ q.bindValue(":priceDate", p.date().toString(Qt::ISODate));
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("finding Price")));
+ if (q.next()) {
+ q.prepare(m_db.m_tables["kmmPrices"].updateString());
+ } else {
+ q.prepare(m_db.m_tables["kmmPrices"].insertString());
+ ++m_prices;
+ newRecord = true;
+ }
+ q.bindValue(":fromId", p.from());
+ q.bindValue(":toId", p.to());
+ q.bindValue(":priceDate", p.date().toString(Qt::ISODate));
+ q.bindValue(":price", p.rate(QString()).toString());
+ q.bindValue(":priceFormatted",
+ p.rate(QString()).formatMoney("", KMyMoneySettings::pricePrecision()));
+ q.bindValue(":priceSource", p.source());
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("writing Price")));
+
+ if (newRecord) writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::removePrice(const MyMoneyPrice& p) {
+ DBG("*** Entering MyMoneyStorageSql::removePrice");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmPrices"].deleteString());
+ q.bindValue(":fromId", p.from());
+ q.bindValue(":toId", p.to());
+ q.bindValue(":priceDate", p.date().toString(Qt::ISODate));
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("deleting Price")));
+ --m_prices;
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::writePrice(const MyMoneyPrice& p) {
+ DBG("*** Entering MyMoneyStorageSql::writePrice");
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmPrices"].insertString());
+ q.bindValue(":fromId", p.from());
+ q.bindValue(":toId", p.to());
+ q.bindValue(":priceDate", p.date().toString(Qt::ISODate));
+ q.bindValue(":price", p.rate(QString()).toString());
+ q.bindValue(":priceFormatted", p.rate(QString()).formatMoney("", 2));
+ q.bindValue(":priceSource", p.source());
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("writing Prices")));
+}
+
+// **** Currencies ****
+void MyMoneyStorageSql::writeCurrencies() {
+ DBG("*** Entering MyMoneyStorageSql::writeCurrencies");
+ // first, get a list of what's on the database (see writeInstitutions)
+ QValueList<QString> dbList;
+ MyMoneySqlQuery q(this);
+ MyMoneySqlQuery q2(this);
+ q.prepare("SELECT ISOCode FROM kmmCurrencies;");
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "building Currency list"));
+ while (q.next()) dbList.append(q.value(0).toString());
+
+ const QValueList<MyMoneySecurity> currencyList = m_storage->currencyList();
+ signalProgress(0, currencyList.count(), "Writing Currencies...");
+ q.prepare (m_db.m_tables["kmmCurrencies"].updateString());
+ q2.prepare (m_db.m_tables["kmmCurrencies"].insertString());
+ for(QValueList<MyMoneySecurity>::ConstIterator it = currencyList.begin(); it != currencyList.end(); ++it) {
+ if (dbList.contains((*it).id())) {
+ dbList.remove ((*it).id());
+ writeCurrency((*it), q);
+ } else {
+ writeCurrency((*it), q2);
+ }
+ signalProgress(++m_currencies, 0);
+ }
+
+ if (!dbList.isEmpty()) {
+ q.prepare("DELETE FROM kmmCurrencies WHERE ISOCode = :ISOCode");
+ QValueList<QString>::const_iterator it = dbList.begin();
+ while (it != dbList.end()) {
+ q.bindValue(":ISOCode", (*it));
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Currency"));
+ ++it;
+ }
+ }
+}
+
+void MyMoneyStorageSql::addCurrency(const MyMoneySecurity& sec) {
+ DBG("*** Entering MyMoneyStorageSql::addCurrency");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmCurrencies"].insertString());
+ writeCurrency(sec,q);
+ ++m_currencies;
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::modifyCurrency(const MyMoneySecurity& sec) {
+ DBG("*** Entering MyMoneyStorageSql::modifyCurrency");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmCurrencies"].updateString());
+ writeCurrency(sec,q);
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::removeCurrency(const MyMoneySecurity& sec) {
+ DBG("*** Entering MyMoneyStorageSql::removeCurrency");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmCurrencies"].deleteString());
+ q.bindValue(":ISOcode", sec.id());
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("deleting Currency")));
+ --m_currencies;
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::writeCurrency(const MyMoneySecurity& currency, MyMoneySqlQuery& q) {
+ DBG("*** Entering MyMoneyStorageSql::writeCurrency");
+ q.bindValue(":ISOcode", currency.id());
+ q.bindValue(":name", currency.name());
+ q.bindValue(":type", static_cast<int>(currency.securityType()));
+ q.bindValue(":typeString", MyMoneySecurity::securityTypeToString(currency.securityType()));
+ // writing the symbol as three short ints is a PITA, but the
+ // problem is that database drivers have incompatible ways of declaring UTF8
+ QString symbol = currency.tradingSymbol() + " ";
+ q.bindValue(":symbol1", symbol.mid(0,1).unicode()->unicode());
+ q.bindValue(":symbol2", symbol.mid(1,1).unicode()->unicode());
+ q.bindValue(":symbol3", symbol.mid(2,1).unicode()->unicode());
+ q.bindValue(":symbolString", symbol);
+ q.bindValue(":partsPerUnit", currency.partsPerUnit());
+ q.bindValue(":smallestCashFraction", currency.smallestCashFraction());
+ q.bindValue(":smallestAccountFraction", currency.smallestAccountFraction());
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("writing Currencies")));
+}
+
+
+void MyMoneyStorageSql::writeReports() {
+ DBG("*** Entering MyMoneyStorageSql::writeReports");
+ // first, get a list of what's on the database (see writeInstitutions)
+ QValueList<QString> dbList;
+ MyMoneySqlQuery q(this);
+ MyMoneySqlQuery q2(this);
+ q.prepare("SELECT id FROM kmmReportConfig;");
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "building Report list"));
+ while (q.next()) dbList.append(q.value(0).toString());
+
+ QValueList<MyMoneyReport> list = m_storage->reportList();
+ signalProgress(0, list.count(), "Writing Reports...");
+ QValueList<MyMoneyReport>::ConstIterator it;
+ q.prepare (m_db.m_tables["kmmReportConfig"].updateString());
+ q2.prepare (m_db.m_tables["kmmReportConfig"].insertString());
+ for(it = list.begin(); it != list.end(); ++it){
+ if (dbList.contains((*it).id())) {
+ dbList.remove ((*it).id());
+ writeReport(*it, q);
+ } else {
+ writeReport(*it, q2);
+ }
+ signalProgress(++m_reports, 0);
+ }
+
+ if (!dbList.isEmpty()) {
+ q.prepare("DELETE FROM kmmReportConfig WHERE id = :id");
+ QValueList<QString>::const_iterator it = dbList.begin();
+ while (it != dbList.end()) {
+ q.bindValue(":id", (*it));
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Report"));
+ ++it;
+ }
+ }
+}
+
+void MyMoneyStorageSql::addReport(const MyMoneyReport& rep) {
+ DBG("*** Entering MyMoneyStorageSql::addReport");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmReportConfig"].insertString());
+ writeReport(rep,q);
+ ++m_reports;
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::modifyReport(const MyMoneyReport& rep) {
+ DBG("*** Entering MyMoneyStorageSql::modifyReport");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmReportConfig"].updateString());
+ writeReport(rep,q);
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::removeReport(const MyMoneyReport& rep) {
+ DBG("*** Entering MyMoneyStorageSql::removeReport");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ q.prepare("DELETE FROM kmmReportConfig WHERE id = :id");
+ q.bindValue(":id", rep.id());
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("deleting Report")));
+ --m_reports;
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::writeReport (const MyMoneyReport& rep, MyMoneySqlQuery& q) {
+ DBG("*** Entering MyMoneyStorageSql::writeReport");
+ QDomDocument d; // create a dummy XML document
+ QDomElement e = d.createElement("REPORTS");
+ d.appendChild (e);
+ rep.writeXML(d, e); // write the XML to document
+ q.bindValue(":id", rep.id());
+ q.bindValue(":name", rep.name());
+ q.bindValue(":XML", d.toString());
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("writing Reports")));
+ //m_hiIdReports = calcHighId(m_hiIdReports, rep.id());
+}
+
+void MyMoneyStorageSql::writeBudgets() {
+ DBG("*** Entering MyMoneyStorageSql::writeBudgets");
+ // first, get a list of what's on the database (see writeInstitutions)
+ QValueList<QString> dbList;
+ MyMoneySqlQuery q(this);
+ MyMoneySqlQuery q2(this);
+ q.prepare("SELECT name FROM kmmBudgetConfig;");
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "building Budget list"));
+ while (q.next()) dbList.append(q.value(0).toString());
+
+ QValueList<MyMoneyBudget> list = m_storage->budgetList();
+ signalProgress(0, list.count(), "Writing Budgets...");
+ QValueList<MyMoneyBudget>::ConstIterator it;
+ q.prepare (m_db.m_tables["kmmBudgetConfig"].updateString());
+ q2.prepare (m_db.m_tables["kmmBudgetConfig"].insertString());
+ for(it = list.begin(); it != list.end(); ++it){
+ if (dbList.contains((*it).name())) {
+ dbList.remove ((*it).name());
+ writeBudget(*it, q);
+ } else {
+ writeBudget(*it, q2);
+ }
+ signalProgress(++m_budgets, 0);
+ }
+
+ if (!dbList.isEmpty()) {
+ q.prepare("DELETE FROM kmmBudgetConfig WHERE id = :id");
+ QValueList<QString>::const_iterator it = dbList.begin();
+ while (it != dbList.end()) {
+ q.bindValue(":name", (*it));
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Budget"));
+ ++it;
+ }
+ }
+}
+
+void MyMoneyStorageSql::addBudget(const MyMoneyBudget& bud) {
+ DBG("*** Entering MyMoneyStorageSql::addBudget");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmBudgetConfig"].insertString());
+ writeBudget(bud,q);
+ ++m_budgets;
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::modifyBudget(const MyMoneyBudget& bud) {
+ DBG("*** Entering MyMoneyStorageSql::modifyBudget");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmBudgetConfig"].updateString());
+ writeBudget(bud,q);
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::removeBudget(const MyMoneyBudget& bud) {
+ DBG("*** Entering MyMoneyStorageSql::removeBudget");
+ startCommitUnit(__func__);
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmBudgetConfig"].deleteString());
+ q.bindValue(":id", bud.id());
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("deleting Budget")));
+ --m_budgets;
+ writeFileInfo();
+ endCommitUnit(__func__);
+}
+
+void MyMoneyStorageSql::writeBudget (const MyMoneyBudget& bud, MyMoneySqlQuery& q) {
+ DBG("*** Entering MyMoneyStorageSql::writeBudget");
+ QDomDocument d; // create a dummy XML document
+ QDomElement e = d.createElement("BUDGETS");
+ d.appendChild (e);
+ bud.writeXML(d, e); // write the XML to document
+ q.bindValue(":id", bud.id());
+ q.bindValue(":name", bud.name());
+ q.bindValue(":start", bud.budgetStart());
+ q.bindValue(":XML", d.toString());
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("writing Budgets")));
+}
+
+void MyMoneyStorageSql::writeFileInfo() {
+ DBG("*** Entering MyMoneyStorageSql::writeFileInfo");
+ // we have no real way of knowing when these change, so re-write them every time
+ deleteKeyValuePairs("STORAGE", "");
+ writeKeyValuePairs("STORAGE", "", m_storage->pairs());
+ //
+ MyMoneySqlQuery q(this);
+ q.prepare ("SELECT * FROM kmmFileInfo;");
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "checking fileinfo"));
+ QString qs;
+ if (q.next())
+ qs = m_db.m_tables["kmmFileInfo"].updateString();
+ else
+ qs = (m_db.m_tables["kmmFileInfo"].insertString());
+ q.prepare(qs);
+ q.bindValue(":version", m_dbVersion);
+ q.bindValue(":fixLevel", m_storage->fileFixVersion());
+ q.bindValue(":created", m_storage->creationDate().toString(Qt::ISODate));
+ //q.bindValue(":lastModified", m_storage->lastModificationDate().toString(Qt::ISODate));
+ q.bindValue(":lastModified", QDate::currentDate().toString(Qt::ISODate));
+ q.bindValue(":baseCurrency", m_storage->pairs()["kmm-baseCurrency"]);
+ q.bindValue(":institutions", (unsigned long long) m_institutions);
+ q.bindValue(":accounts", (unsigned long long) m_accounts);
+ q.bindValue(":payees", (unsigned long long) m_payees);
+ q.bindValue(":transactions", (unsigned long long) m_transactions);
+ q.bindValue(":splits", (unsigned long long) m_splits);
+ q.bindValue(":securities", (unsigned long long) m_securities);
+ q.bindValue(":prices", (unsigned long long) m_prices);
+ q.bindValue(":currencies", (unsigned long long) m_currencies);
+ q.bindValue(":schedules", (unsigned long long) m_schedules);
+ q.bindValue(":reports", (unsigned long long) m_reports);
+ q.bindValue(":kvps", (unsigned long long) m_kvps);
+ q.bindValue(":budgets", (unsigned long long) m_budgets);
+ q.bindValue(":dateRangeStart", QDate());
+ q.bindValue(":dateRangeEnd", QDate());
+
+ //FIXME: This modifies all m_<variable> used in this function.
+ // Sometimes the memory has been updated.
+
+ // Should most of these be tracked in a view?
+ // Variables actually needed are: version, fileFixVersion, creationDate,
+ // baseCurrency, encryption, update info, and logon info.
+ try {
+ //readFileInfo();
+ } catch (...) {
+ startCommitUnit(__func__);
+ }
+
+ q.bindValue(":hiInstitutionId", (unsigned long long) m_hiIdInstitutions);
+ q.bindValue(":hiPayeeId", (unsigned long long) m_hiIdPayees);
+ q.bindValue(":hiAccountId", (unsigned long long) m_hiIdAccounts);
+ q.bindValue(":hiTransactionId", (unsigned long long) m_hiIdTransactions);
+ q.bindValue(":hiScheduleId", (unsigned long long) m_hiIdSchedules);
+ q.bindValue(":hiSecurityId", (unsigned long long) m_hiIdSecurities);
+ q.bindValue(":hiReportId", (unsigned long long) m_hiIdReports);
+ q.bindValue(":hiBudgetId", (unsigned long long) m_hiIdBudgets);
+
+ q.bindValue(":encryptData", m_encryptData);
+ q.bindValue(":updateInProgress", "N");
+ q.bindValue(":logonUser", m_logonUser);
+ q.bindValue(":logonAt", m_logonAt.toString(Qt::ISODate));
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("writing FileInfo")));
+}
+
+// **** Key/value pairs ****
+void MyMoneyStorageSql::writeKeyValuePairs(const QString& kvpType, const QString& kvpId, const QMap<QString, QString>& pairs) {
+ DBG("*** Entering MyMoneyStorageSql::writeKeyValuePairs");
+ QMap<QString, QString>::const_iterator it;
+ for(it = pairs.begin(); it != pairs.end(); ++it) {
+ writeKeyValuePair (kvpType, kvpId, it.key(), it.data());
+ }
+}
+
+void MyMoneyStorageSql::writeKeyValuePair (const QString& kvpType, const QString& kvpId, const QString& kvpKey, const QString& kvpData) {
+ DBG("*** Entering MyMoneyStorageSql::writeKeyValuePair");
+ MyMoneySqlQuery q(this);
+ q.prepare (m_db.m_tables["kmmKeyValuePairs"].insertString());
+ q.bindValue(":kvpType", kvpType);
+ q.bindValue(":kvpId", kvpId);
+ q.bindValue(":kvpKey", kvpKey);
+ q.bindValue(":kvpData", kvpData);
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("writing KVP")));
+ ++m_kvps;
+}
+
+void MyMoneyStorageSql::deleteKeyValuePairs (const QString& kvpType, const QString& kvpId) {
+ DBG("*** Entering MyMoneyStorageSql::deleteKeyValuePairs");
+ MyMoneySqlQuery q(this);
+ q.prepare ("DELETE FROM kmmKeyValuePairs WHERE kvpType = :kvpType AND kvpId = :kvpId;");
+ q.bindValue(":kvpType", kvpType);
+ q.bindValue(":kvpId", kvpId);
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("deleting kvp for %1 %2").arg(kvpType).arg(kvpId)));
+ m_kvps -= q.numRowsAffected();
+}
+
+//******************************** read SQL routines **************************************
+#define CASE(a) if ((*ft)->name() == #a)
+#define GETSTRING q.value(i).toString()
+#define GETCSTRING q.value(i).toCString()
+#define GETDATE getDate(GETSTRING)
+#define GETDATETIME getDateTime(GETSTRING)
+#define GETINT q.value(i).toInt()
+#define GETULL q.value(i).toULongLong()
+
+void MyMoneyStorageSql::readFileInfo(void) {
+ DBG("*** Entering MyMoneyStorageSql::readFileInfo");
+ signalProgress(0, 18, QObject::tr("Loading file information..."));
+ MyMoneyDbTable& t = m_db.m_tables["kmmFileInfo"];
+ MyMoneySqlQuery q(this);
+ q.prepare (t.selectAllString());
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("reading FileInfo")));
+ if (!q.next()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("retrieving FileInfo")));
+ MyMoneyDbTable::field_iterator ft = t.begin();
+ int i = 0;
+ while (ft != t.end()) {
+ // versioning is now handled in open routine
+/* CASE(version) setVersion(GETSTRING); // check version == current version...
+ else*/
+ CASE(created) m_storage->setCreationDate(GETDATE);
+ else CASE(lastModified) m_storage->setLastModificationDate(GETDATE);
+ else CASE(hiInstitutionId) m_hiIdInstitutions = (unsigned long) GETULL;
+ else CASE(hiPayeeId) m_hiIdPayees = (unsigned long) GETULL;
+ else CASE(hiAccountId) m_hiIdAccounts = (unsigned long) GETULL;
+ else CASE(hiTransactionId) m_hiIdTransactions = (unsigned long) GETULL;
+ else CASE(hiScheduleId) m_hiIdSchedules = (unsigned long) GETULL;
+ else CASE(hiSecurityId) m_hiIdSecurities = (unsigned long) GETULL;
+ else CASE(hiReportId ) m_hiIdReports = (unsigned long) GETULL;
+ else CASE(hiBudgetId ) m_hiIdBudgets = (unsigned long) GETULL;
+ else CASE(institutions) m_institutions = (unsigned long) GETULL;
+ else CASE(accounts ) m_accounts = (unsigned long) GETULL;
+ else CASE(payees ) m_payees = (unsigned long) GETULL;
+ else CASE(transactions) m_transactions = (unsigned long) GETULL;
+ else CASE(splits ) m_splits = (unsigned long) GETULL;
+ else CASE(securities ) m_securities = (unsigned long) GETULL;
+ else CASE(currencies ) m_currencies = (unsigned long) GETULL;
+ else CASE(schedules ) m_schedules = (unsigned long) GETULL;
+ else CASE(prices ) m_prices = (unsigned long) GETULL;
+ else CASE(kvps ) m_kvps = (unsigned long) GETULL;
+ else CASE(reports ) m_reports = (unsigned long) GETULL;
+ else CASE(budgets ) m_budgets = (unsigned long) GETULL;
+ else CASE(encryptData) m_encryptData = GETSTRING;
+ else CASE(logonUser) m_logonUser = GETSTRING;
+ else CASE(logonAt) m_logonAt = GETDATETIME;
+ ++ft; ++i;
+ signalProgress(i,0);
+ }
+ m_storage->setPairs(readKeyValuePairs("STORAGE", QString("")).pairs());
+}
+
+/*void MyMoneyStorageSql::setVersion (const QString& version) {
+ DBG("*** Entering MyMoneyStorageSql::setVersion");
+ m_dbVersion = version.section('.', 0, 0).toUInt();
+ m_minorVersion = version.section('.', 1, 1).toUInt();
+ // Okay, I made a cockup by forgetting to include a fixversion in the database
+ // design, so we'll use the minor version as fix level (similar to VERSION
+ // and FIXVERSION in XML file format). A second mistake was setting minor version to 1
+ // in the first place, so we need to subtract one on reading and add one on writing (sigh)!!
+ m_storage->setFileFixVersion( m_minorVersion - 1);
+}*/
+
+void MyMoneyStorageSql::readInstitutions(void) {
+ TRY
+ QMap<QString, MyMoneyInstitution> iList = fetchInstitutions();
+ m_storage->loadInstitutions(iList);
+ readFileInfo();
+ m_storage->loadInstitutionId(m_hiIdInstitutions);
+ PASS
+}
+
+const QMap<QString, MyMoneyInstitution> MyMoneyStorageSql::fetchInstitutions (const QStringList& idList, bool forUpdate) const {
+ DBG("*** Entering MyMoneyStorageSql::readInstitutions");
+ signalProgress(0, m_institutions, QObject::tr("Loading institutions..."));
+ int progress = 0;
+ QMap<QString, MyMoneyInstitution> iList;
+ unsigned long lastId = 0;
+ const MyMoneyDbTable& t = m_db.m_tables["kmmInstitutions"];
+ MyMoneySqlQuery sq(const_cast <MyMoneyStorageSql*> (this));
+ sq.prepare ("SELECT id from kmmAccounts where institutionId = :id");
+ MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
+ QString queryString (t.selectAllString(false));
+
+ // Use bind variables, instead of just inserting the values in the queryString,
+ // so that values containing a ':' will work.
+ if (! idList.empty()) {
+ queryString += " WHERE";
+ for (unsigned i = 0; i < idList.count(); ++i)
+ queryString += " id = :id" + QString::number(i) + " OR";
+ queryString = queryString.left(queryString.length() - 2);
+ }
+ if (forUpdate)
+ queryString += " FOR UPDATE";
+
+ queryString += ";";
+
+ q.prepare (queryString);
+
+ if (! idList.empty()) {
+ QStringList::const_iterator bindVal = idList.begin();
+ for (int i = 0; bindVal != idList.end(); ++i, ++bindVal) {
+ q.bindValue (":id" + QString::number(i), *bindVal);
+ }
+ }
+
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("reading Institution")));
+ while (q.next()) {
+ MyMoneyDbTable::field_iterator ft = t.begin();
+ int i = 0;
+ QString iid;
+ MyMoneyInstitution inst;
+ while (ft != t.end()) {
+ CASE(id) iid = GETSTRING;
+ else CASE(name) inst.setName(GETSTRING);
+ else CASE(manager) inst.setManager(GETSTRING);
+ else CASE(routingCode) inst.setSortcode(GETSTRING);
+ else CASE(addressStreet) inst.setStreet(GETSTRING);
+ else CASE(addressCity) inst.setCity(GETSTRING);
+ else CASE(addressZipcode) inst.setPostcode(GETSTRING);
+ else CASE(telephone) inst.setTelephone(GETSTRING);
+ ++ft; ++i;
+ }
+ // get list of subaccounts
+ sq.bindValue(":id", iid);
+ if (!sq.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("reading Institution AccountList")));
+ QStringList aList;
+ while (sq.next()) aList.append(sq.value(0).toString());
+ for (QStringList::ConstIterator it = aList.begin(); it != aList.end(); ++it)
+ inst.addAccountId(*it);
+
+ iList[iid] = MyMoneyInstitution(iid, inst);
+ unsigned long id = extractId(iid);
+ if(id > lastId)
+ lastId = id;
+
+ signalProgress (++progress, 0);
+ }
+ return iList;
+}
+
+void MyMoneyStorageSql::readPayees (const QString& id) {
+ DBG("*** Entering MyMoneyStorageSql::readPayees");
+ QValueList<QString> list;
+ list.append(id);
+ readPayees(list);
+}
+
+void MyMoneyStorageSql::readPayees(const QValueList<QString> pid) {
+ DBG("*** Entering MyMoneyStorageSql::readPayees");
+ TRY
+ QStringList pidList;
+ qCopy(pid.begin(), pid.end(), qBackInserter(pidList));
+
+ m_storage->loadPayees(fetchPayees(pidList));
+ readFileInfo();
+ m_storage->loadPayeeId(m_hiIdPayees);
+ CATCH
+ delete e; // ignore duplicates
+ ECATCH
+// if (pid.isEmpty()) m_payeeListRead = true;
+}
+
+const QMap<QString, MyMoneyPayee> MyMoneyStorageSql::fetchPayees (const QStringList& idList, bool /*forUpdate*/) const {
+ DBG("*** Entering MyMoneyStorageSql::readPayees");
+ if (m_displayStatus) {
+ signalProgress(0, m_payees, QObject::tr("Loading payees..."));
+ } else {
+// if (m_payeeListRead) return;
+ }
+ int progress = 0;
+ QMap<QString, MyMoneyPayee> pList;
+ //unsigned long lastId;
+ const MyMoneyDbTable& t = m_db.m_tables["kmmPayees"];
+ MyMoneyDbTable::field_iterator payeeEnd = t.end();
+ MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
+ if (idList.isEmpty()) {
+ q.prepare (t.selectAllString());
+ } else {
+ QString whereClause = " where (";
+ QString itemConnector = "";
+ QStringList::ConstIterator it;
+ for (it = idList.begin(); it != idList.end(); ++it) {
+ whereClause.append(QString("%1id = '%2'").arg(itemConnector).arg(*it));
+ itemConnector = " or ";
+ }
+ whereClause += ")";
+ q.prepare (t.selectAllString(false) + whereClause);
+ }
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("reading Payee")));
+ while (q.next()) {
+ MyMoneyDbTable::field_iterator ft = t.begin();
+ int i = 0;
+ QString pid;
+ QString boolChar;
+ MyMoneyPayee payee;
+ unsigned int type;
+ bool ignoreCase;
+ QString matchKeys;
+ while (ft != payeeEnd) {
+ CASE(id) pid = GETCSTRING;
+ else CASE(name) payee.setName(GETSTRING);
+ else CASE(reference) payee.setReference(GETSTRING);
+ else CASE(email) payee.setEmail(GETSTRING);
+ else CASE(addressStreet) payee.setAddress(GETSTRING);
+ else CASE(addressCity) payee.setCity(GETSTRING);
+ else CASE(addressZipcode) payee.setPostcode(GETSTRING);
+ else CASE(addressState) payee.setState(GETSTRING);
+ else CASE(telephone) payee.setTelephone(GETSTRING);
+ else CASE(notes) payee.setNotes(GETSTRING);
+ else CASE(defaultAccountId) payee.setDefaultAccountId(GETSTRING);
+ else CASE(matchData) type = GETINT;
+ else CASE(matchIgnoreCase) ignoreCase = (GETSTRING == "Y");
+ else CASE(matchKeys) matchKeys = GETSTRING;
+ ++ft; ++i;
+ }
+ payee.setMatchData (static_cast<MyMoneyPayee::payeeMatchType>(type), ignoreCase, matchKeys);
+ if (pid == "USER") {
+ TRY
+ m_storage->setUser(payee);
+ PASS
+ } else {
+ pList[pid] = MyMoneyPayee(pid, payee);
+ //unsigned long id = extractId(QString(pid));
+ //if(id > lastId)
+ // lastId = id;
+ }
+ if (m_displayStatus) signalProgress(++progress, 0);
+ }
+ return pList;
+}
+
+const QMap<QString, MyMoneyAccount> MyMoneyStorageSql::fetchAccounts (const QStringList& idList, bool forUpdate) const {
+ DBG("*** Entering MyMoneyStorageSql::fetchAccounts");
+ signalProgress(0, m_accounts, QObject::tr("Loading accounts..."));
+ int progress = 0;
+ QMap<QString, MyMoneyAccount> accList;
+ QStringList kvpAccountList;
+
+ const MyMoneyDbTable& t = m_db.m_tables["kmmAccounts"];
+ MyMoneyDbTable::field_iterator accEnd = t.end();
+ MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
+ MyMoneySqlQuery sq(const_cast <MyMoneyStorageSql*> (this));
+
+ QString childQueryString = "SELECT id, parentId FROM kmmAccounts WHERE ";
+ QString queryString (t.selectAllString(false));
+
+ // Use bind variables, instead of just inserting the values in the queryString,
+ // so that values containing a ':' will work.
+ if (! idList.empty()) {
+ kvpAccountList = idList;
+ queryString += " WHERE id IN (";
+ childQueryString += " parentId IN (";
+ for (unsigned i = 0; i < idList.count(); ++i) {
+ queryString += " :id" + QString::number(i) + ", ";
+ childQueryString += ":id" + QString::number(i) + ", ";
+ }
+ queryString = queryString.left(queryString.length() - 2) + ")";
+ childQueryString = childQueryString.left(childQueryString.length() - 2) + ")";
+ } else {
+ childQueryString += " NOT parentId IS NULL";
+ }
+
+ queryString += " ORDER BY id";
+ childQueryString += " ORDER BY parentid, id";
+
+ if (forUpdate) {
+ queryString += " FOR UPDATE";
+ childQueryString += " FOR UPDATE";
+ }
+
+ q.prepare (queryString);
+ sq.prepare (childQueryString);
+
+ if (! idList.empty()) {
+ QStringList::const_iterator bindVal = idList.begin();
+ for (int i = 0; bindVal != idList.end(); ++i, ++bindVal) {
+ q.bindValue (":id" + QString::number(i), *bindVal);
+ sq.bindValue (":id" + QString::number(i), *bindVal);
+ }
+ }
+
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("reading Account")));
+ if (!sq.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("reading subAccountList")));
+ while (q.next()) {
+ MyMoneyDbTable::field_iterator ft = t.begin();
+ int i = 0;
+ QString aid;
+ QString balance;
+ MyMoneyAccount acc;
+
+ while (ft != accEnd) {
+ CASE(id) aid = GETCSTRING;
+ else CASE(institutionId) acc.setInstitutionId(GETCSTRING);
+ else CASE(parentId) acc.setParentAccountId(GETCSTRING);
+ else CASE(lastReconciled) acc.setLastReconciliationDate(GETDATE);
+ else CASE(lastModified) acc.setLastModified(GETDATE);
+ else CASE(openingDate) acc.setOpeningDate(GETDATE);
+ else CASE(accountNumber) acc.setNumber(GETSTRING);
+ else CASE(accountType) acc.setAccountType(static_cast<MyMoneyAccount::accountTypeE>(GETINT));
+ else CASE(accountName) acc.setName(GETSTRING);
+ else CASE(description) acc.setDescription(GETSTRING);
+ else CASE(currencyId) acc.setCurrencyId(GETCSTRING);
+ else CASE(balance) acc.setBalance(GETSTRING);
+ else CASE(transactionCount)
+ const_cast <MyMoneyStorageSql*> (this)->m_transactionCountMap[aid] = (unsigned long) GETULL;
+ ++ft; ++i;
+ }
+
+ // Process any key value pair
+ if (idList.empty())
+ kvpAccountList.append(aid);
+
+ // in database mode, load the balance from the account record
+ // else we would need to read all the transactions
+ accList.insert(aid, MyMoneyAccount(aid, acc));
+ if (acc.value("PreferredAccount") == "Yes") {
+ const_cast <MyMoneyStorageSql*> (this)->m_preferred.addAccount(aid);
+ }
+ signalProgress(++progress, 0);
+ }
+
+ QMapIterator<QString, MyMoneyAccount> it_acc;
+ QMapIterator<QString, MyMoneyAccount> accListEnd = accList.end();
+ while (sq.next()) {
+ it_acc = accList.find(sq.value(1).toString());
+ if (it_acc != accListEnd && it_acc.data().id() == sq.value(1).toString()) {
+ while (sq.isValid() && it_acc != accListEnd
+ && it_acc.data().id() == sq.value(1).toString()) {
+ it_acc.data().addAccountId(sq.value(0).toString());
+ sq.next();
+ }
+ sq.prev();
+ }
+ }
+
+ //TODO: There should be a better way than this. What's below is O(n log n) or more,
+ // where it may be able to be done in O(n), if things are just right.
+ // The operator[] call in the loop is the most expensive call in this function, according
+ // to several profile runs.
+ QMap <QString, MyMoneyKeyValueContainer> kvpResult = readKeyValuePairs("ACCOUNT", kvpAccountList);
+ QMap <QString, MyMoneyKeyValueContainer>::const_iterator kvp_end = kvpResult.end();
+ for (QMap <QString, MyMoneyKeyValueContainer>::const_iterator it_kvp = kvpResult.begin();
+ it_kvp != kvp_end; ++it_kvp) {
+ accList[it_kvp.key()].setPairs(it_kvp.data().pairs());
+ }
+
+ kvpResult = readKeyValuePairs("ONLINEBANKING", kvpAccountList);
+ kvp_end = kvpResult.end();
+ for (QMap <QString, MyMoneyKeyValueContainer>::const_iterator it_kvp = kvpResult.begin();
+ it_kvp != kvp_end; ++it_kvp) {
+ accList[it_kvp.key()].setOnlineBankingSettings(it_kvp.data());
+ }
+
+ return accList;
+}
+
+void MyMoneyStorageSql::readAccounts(void) {
+ m_storage->loadAccounts(fetchAccounts());
+ m_storage->loadAccountId(m_hiIdAccounts);
+}
+
+const QMap<QString, MyMoneyMoney> MyMoneyStorageSql::fetchBalance(const QStringList& idList, const QDate& date) const {
+
+ QMap<QString, MyMoneyMoney> returnValue;
+ MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
+ QString queryString = "SELECT action, shares, accountId, postDate "
+ "FROM kmmSplits WHERE txType = 'N' AND accountId in (";
+
+ for (unsigned i = 0; i < idList.count(); ++i) {
+ queryString += " :id" + QString::number(i) + ", ";
+ }
+ queryString = queryString.left(queryString.length() - 2) + " )";
+
+ // SQLite stores dates as YYYY-MM-DDTHH:mm:ss with 0s for the time part. This makes
+ // the <= operator misbehave when the date matches. To avoid this, add a day to the
+ // requested date and use the < operator.
+ if (date.isValid() && !date.isNull())
+ queryString += QString(" AND postDate < '%1'").arg(date.addDays(1).toString(Qt::ISODate));
+ DBG (queryString);
+ q.prepare(queryString);
+
+ QStringList::const_iterator bindVal = idList.begin();
+ for (int i = 0; bindVal != idList.end(); ++i, ++bindVal) {
+ q.bindValue (":id" + QString::number(i), *bindVal);
+ returnValue[*bindVal] = MyMoneyMoney(0);
+ }
+ if (!q.exec())
+ throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("fetching balance")));
+ QString id;
+ QString shares;
+ QString action;
+ while (q.next()) {
+ id = q.value(2).toString();
+ shares = q.value(1).toString();
+ action = q.value(0).toString();
+ if (MyMoneySplit::ActionSplitShares == action)
+ returnValue[id] = returnValue[id] * MyMoneyMoney(shares);
+ else
+ returnValue[id] += MyMoneyMoney(shares);
+ }
+ return returnValue;
+}
+
+void MyMoneyStorageSql::readTransactions(const QString& tidList, const QString& dateClause) {
+ TRY
+ m_storage->loadTransactions(fetchTransactions(tidList, dateClause));
+ m_storage->loadTransactionId(m_hiIdTransactions);
+ PASS
+}
+
+void MyMoneyStorageSql::readTransactions(const MyMoneyTransactionFilter& filter) {
+ TRY
+ m_storage->loadTransactions(fetchTransactions(filter));
+ m_storage->loadTransactionId(m_hiIdTransactions);
+ PASS
+}
+
+const QMap<QString, MyMoneyTransaction> MyMoneyStorageSql::fetchTransactions (const QString& tidList, const QString& dateClause, bool /*forUpdate*/) const {
+ DBG("*** Entering MyMoneyStorageSql::readTransactions");
+// if (m_transactionListRead) return; // all list already in memory
+ if (m_displayStatus) signalProgress(0, m_transactions, QObject::tr("Loading transactions..."));
+ int progress = 0;
+// m_payeeList.clear();
+ QString whereClause;
+ whereClause = " WHERE txType = 'N' ";
+ if (! tidList.isEmpty()) {
+ whereClause += " AND id IN " + tidList;
+ }
+ if (!dateClause.isEmpty()) whereClause += " and " + dateClause;
+ const MyMoneyDbTable& t = m_db.m_tables["kmmTransactions"];
+ MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
+ q.prepare (t.selectAllString(false) + whereClause + " ORDER BY id;");
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("reading Transaction")));
+ const MyMoneyDbTable& ts = m_db.m_tables["kmmSplits"];
+ whereClause = " WHERE txType = 'N' ";
+ if (! tidList.isEmpty()) {
+ whereClause += " AND transactionId IN " + tidList;
+ }
+ if (!dateClause.isEmpty()) whereClause += " and " + dateClause;
+ MyMoneySqlQuery qs(const_cast <MyMoneyStorageSql*> (this));
+ QString splitQuery = ts.selectAllString(false) + whereClause
+ + " ORDER BY transactionId, splitId;";
+ qs.prepare (splitQuery);
+ if (!qs.exec()) throw new MYMONEYEXCEPTION(buildError (qs, __func__, "reading Splits"));
+ QString splitTxId = "ZZZ";
+ MyMoneySplit s;
+ if (qs.next()) {
+ splitTxId = qs.value(0).toString();
+ readSplit (s, qs, ts);
+ } else {
+ splitTxId = "ZZZ";
+ }
+ QMap <QString, MyMoneyTransaction> txMap;
+ QStringList txList;
+ MyMoneyDbTable::field_iterator txEnd = t.end();
+ while (q.next()) {
+ MyMoneyTransaction tx;
+ QString txId;
+ MyMoneyDbTable::field_iterator ft = t.begin();
+ int i = 0;
+ while (ft != txEnd) {
+ CASE(id) txId = GETSTRING;
+ else CASE(postDate) tx.setPostDate(GETDATE);
+ else CASE(memo) tx.setMemo(GETSTRING);
+ else CASE(entryDate) tx.setEntryDate(GETDATE);
+ else CASE(currencyId) tx.setCommodity(GETCSTRING);
+ else CASE(bankId) tx.setBankID(GETSTRING);
+ ++ft; ++i;
+ }
+
+ while (txId < splitTxId && splitTxId != "ZZZ") {
+ if (qs.next()) {
+ splitTxId = qs.value(0).toString();
+ readSplit (s, qs, ts);
+ } else {
+ splitTxId = "ZZZ";
+ }
+ }
+
+ while (txId == splitTxId) {
+ tx.addSplit (s);
+ if (qs.next()) {
+ splitTxId = qs.value(0).toString();
+ readSplit (s, qs, ts);
+ } else {
+ splitTxId = "ZZZ";
+ }
+ }
+ // Process any key value pair
+ if (! txId.isEmpty()) {
+ txList.append(txId);
+ tx = MyMoneyTransaction(txId, tx);
+ txMap.insert(tx.uniqueSortKey(), tx);
+ }
+ }
+ QMap <QString, MyMoneyKeyValueContainer> kvpMap = readKeyValuePairs("TRANSACTION", txList);
+ QMap<QString, MyMoneyTransaction> tList;
+ QMapIterator<QString, MyMoneyTransaction> txMapEnd = txMap.end();
+ for (QMapIterator<QString, MyMoneyTransaction> i = txMap.begin();
+ i != txMapEnd; ++i) {
+ i.data().setPairs(kvpMap[i.data().id()].pairs());
+
+ if (m_displayStatus) signalProgress(++progress, 0);
+ }
+
+ if ((tidList.isEmpty()) && (dateClause.isEmpty())) {
+ //qDebug("setting full list read");
+ }
+ return txMap;
+}
+
+int MyMoneyStorageSql::splitState(const MyMoneyTransactionFilter::stateOptionE& state) const
+{
+ int rc = MyMoneySplit::NotReconciled;
+
+ switch(state) {
+ default:
+ case MyMoneyTransactionFilter::notReconciled:
+ break;
+
+ case MyMoneyTransactionFilter::cleared:
+ rc = MyMoneySplit::Cleared;
+ break;
+
+ case MyMoneyTransactionFilter::reconciled:
+ rc = MyMoneySplit::Reconciled;
+ break;
+
+ case MyMoneyTransactionFilter::frozen:
+ rc = MyMoneySplit::Frozen;
+ break;
+ }
+ return rc;
+}
+
+const QMap<QString, MyMoneyTransaction> MyMoneyStorageSql::fetchTransactions (const MyMoneyTransactionFilter& filter) const {
+ DBG("*** Entering MyMoneyStorageSql::readTransactions");
+ // analyze the filter
+// if (m_transactionListRead) return; // all list already in memory
+ // if the filter is restricted to certain accounts/categories
+ // check if we already have them all in memory
+ QStringList accounts;
+ QString inQuery;
+ filter.accounts(accounts);
+ filter.categories(accounts);
+// QStringList::iterator it;
+// bool allAccountsLoaded = true;
+// for (it = accounts.begin(); it != accounts.end(); ++it) {
+// if (m_accountsLoaded.find(*it) == m_accountsLoaded.end()) {
+// allAccountsLoaded = false;
+// break;
+// }
+// }
+// if (allAccountsLoaded) return;
+ /* Some filter combinations do not lend themselves to implementation
+ * in SQL, or are likely to require such extensive reading of the database
+ * as to make it easier to just read everything into memory. */
+ bool canImplementFilter = true;
+ MyMoneyMoney m1, m2;
+ if (filter.amountFilter( m1, m2 )) {
+ alert ("Amount Filter Set");
+ canImplementFilter = false;
+ }
+ QString n1, n2;
+ if (filter.numberFilter(n1, n2)) {
+ alert("Number filter set");
+ canImplementFilter = false;
+ }
+ int t1;
+ if (filter.firstType(t1)) {
+ alert("Type filter set");
+ canImplementFilter = false;
+ }
+// int s1;
+// if (filter.firstState(s1)) {
+// alert("State filter set");
+// canImplementFilter = false;
+// }
+ QRegExp t2;
+ if (filter.textFilter(t2)) {
+ alert("text filter set");
+ canImplementFilter = false;
+ }
+ MyMoneyTransactionFilter::FilterSet s = filter.filterSet();
+ if (s.singleFilter.validityFilter) {
+ alert("Validity filter set");
+ canImplementFilter = false;
+ }
+ if (!canImplementFilter) {
+ QMap<QString, MyMoneyTransaction> transactionList = fetchTransactions();
+ QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
+ QMap<QString, MyMoneyTransaction>::ConstIterator txListEnd = transactionList.end();
+
+ std::remove_if(transactionList.begin(), transactionList.end(), FilterFail(filter, m_storagePtr));
+ return transactionList;
+ }
+
+ bool accountsOnlyFilter = true;
+ bool splitFilterActive = false; // the split filter is active if we are selecting on fields in the split table
+ // get start and end dates
+ QDate start = filter.fromDate();
+ QDate end = filter.toDate();
+ // not entirely sure if the following is correct, but at best, saves a lot of reads, at worst
+ // it only causes us to read a few more transactions that strictly necessary (I think...)
+ if (start == KMyMoneySettings::startDate().date()) start = QDate();
+ bool txFilterActive = ((start != QDate()) || (end != QDate())); // and this for fields in the transaction table
+ if (txFilterActive) accountsOnlyFilter = false;
+
+ QString whereClause = "";
+ QString subClauseconnector = " where txType = 'N' and ";
+ // payees
+ QStringList payees;
+ //filter.payees(payees);
+ if (filter.payees(payees)) {
+ accountsOnlyFilter = false;
+ QString itemConnector = "payeeId in (";
+ QString payeesClause = "";
+ QStringList::const_iterator it;
+ for (it = payees.begin(); it != payees.end(); ++it) {
+ payeesClause.append(QString("%1'%2'")
+ .arg(itemConnector).arg(*it));
+ itemConnector = ", ";
+ }
+ if (!payeesClause.isEmpty()) {
+ whereClause += subClauseconnector + payeesClause + ")";
+ subClauseconnector = " and ";
+ }
+ splitFilterActive = true;
+ }
+
+ // accounts and categories
+ if (!accounts.isEmpty()) {
+ splitFilterActive = true;
+ QString itemConnector = "accountId in (";
+ QString accountsClause = "";
+ QStringList::const_iterator it;
+ for (it = accounts.begin(); it != accounts.end(); ++it) {
+// if (m_accountsLoaded.find(*it) == m_accountsLoaded.end()) {
+ accountsClause.append(QString("%1 '%2'")
+ .arg(itemConnector).arg(*it));
+ itemConnector = ", ";
+ //if (accountsOnlyFilter) m_accountsLoaded.append(*it); // a bit premature...
+// }
+ }
+ if (!accountsClause.isEmpty()) {
+ whereClause += subClauseconnector + accountsClause + ")";
+ subClauseconnector = " and (";
+ }
+ }
+
+ // split states
+ QValueList <int> splitStates;
+ if (filter.states(splitStates)) {
+ splitFilterActive = true;
+ QString itemConnector = " reconcileFlag IN (";
+ QString statesClause = "";
+ for (QValueList<int>::ConstIterator it = splitStates.begin(); it != splitStates.end(); ++it) {
+ statesClause.append(QString(" %1 '%2'")
+ .arg(itemConnector)
+ .arg(splitState(MyMoneyTransactionFilter::stateOptionE(*it))));
+ itemConnector = ",";
+ }
+ if (!statesClause.isEmpty()) {
+ whereClause += subClauseconnector + statesClause + ")";
+ subClauseconnector = " and (";
+ }
+ }
+ // I've given up trying to work out the logic. we keep getting the wrong number of close brackets
+ int obc = whereClause.contains('(');
+ int cbc = whereClause.contains(')');
+ if (cbc > obc) {
+ qFatal("invalid where clause - %s", whereClause.latin1());
+ }
+ while (cbc < obc) {
+ whereClause.append(")");
+ cbc++;
+ }
+ // if the split filter is active, but the where clause is empty
+ // it means we already have all the transactions for the specified filter
+ // in memory, so just exit
+ if ((splitFilterActive) && (whereClause.isEmpty())) {
+ qDebug("all transactions already in storage");
+ return fetchTransactions();
+ }
+
+ // if we have neither a split filter, nor a tx (date) filter
+ // it's effectively a read all
+ if ((!splitFilterActive) && (!txFilterActive)) {
+ //qDebug("reading all transactions");
+ return fetchTransactions();
+ }
+ // build a date clause for the transaction table
+ QString dateClause;
+ QString connector = "";
+ if (end != QDate()) {
+ dateClause = QString("(postDate < '%1')").arg(end.addDays(1).toString(Qt::ISODate));
+ connector = " and ";
+ }
+ if (start != QDate()) {
+ dateClause += QString("%1 (postDate >= '%2')").arg(connector).arg(start.toString(Qt::ISODate));
+ }
+ // now get a list of transaction ids
+ // if we have only a date filter, we need to build the list from the tx table
+ // otherwise we need to build from the split table
+ if (splitFilterActive) {
+ inQuery = QString("(select distinct transactionId from kmmSplits %1)").arg(whereClause);
+ } else {
+ inQuery = QString("(select distinct id from kmmTransactions where %1)").arg(dateClause);
+ txFilterActive = false; // kill off the date filter now
+ }
+
+ return fetchTransactions(inQuery, dateClause);
+ //FIXME: if we have an accounts-only filter, recalc balances on loaded accounts
+}
+
+unsigned long MyMoneyStorageSql::transactionCount (const QString& aid) const {
+ DBG("*** Entering MyMoneyStorageSql::transactionCount");
+ if (aid.length() == 0)
+ return m_transactions;
+ else
+ return m_transactionCountMap[aid];
+}
+
+void MyMoneyStorageSql::readSplit (MyMoneySplit& s, const MyMoneySqlQuery& q, const MyMoneyDbTable& t) const {
+ DBG("*** Entering MyMoneyStorageSql::readSplit");
+ s.clearId();
+ MyMoneyDbTable::field_iterator ft = t.begin();
+ MyMoneyDbTable::field_iterator splitEnd = t.end();
+ int i = 0;
+
+ // Use the QString here instead of CASE, since this is called so often.
+ QString fieldName;
+ while (ft != splitEnd) {
+ fieldName = (*ft)->name();
+ if (fieldName == "payeeId") s.setPayeeId(GETCSTRING);
+ else if (fieldName == "reconcileDate") s.setReconcileDate(GETDATE);
+ else if (fieldName == "action") s.setAction(GETCSTRING);
+ else if (fieldName == "reconcileFlag") s.setReconcileFlag(static_cast<MyMoneySplit::reconcileFlagE>(GETINT));
+ else if (fieldName == "value") s.setValue(MyMoneyMoney(QStringEmpty(GETSTRING)));
+ else if (fieldName == "shares") s.setShares(MyMoneyMoney(QStringEmpty(GETSTRING)));
+ else if (fieldName == "price") s.setPrice(MyMoneyMoney(QStringEmpty(GETSTRING)));
+ else if (fieldName == "memo") s.setMemo(GETSTRING);
+ else if (fieldName == "accountId") s.setAccountId(GETCSTRING);
+ else if (fieldName == "checkNumber") s.setNumber(GETSTRING);
+ //else if (fieldName == "postDate") s.setPostDate(GETDATETIME); // FIXME - when Tom puts date into split object
+ else if (fieldName == "bankId") s.setBankID(GETSTRING);
+ ++ft; ++i;
+ }
+
+ return;
+}
+
+bool MyMoneyStorageSql::isReferencedByTransaction(const QString& id) const {
+ DBG("*** Entering MyMoneyStorageSql::isReferencedByTransaction");
+ MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
+ q.prepare("SELECT COUNT(*) FROM kmmTransactions "
+ "INNER JOIN kmmSplits ON kmmTransactions.id = kmmSplits.transactionId "
+ "WHERE kmmTransactions.currencyId = :ID OR kmmSplits.payeeId = :ID "
+ "OR kmmSplits.accountId = :ID");
+ q.bindValue(":ID", id);
+ if ((!q.exec()) || (!q.next())) {
+ buildError (q, __func__, "error retrieving reference count");
+ qFatal("Error retrieving reference count"); // definitely shouldn't happen
+ }
+ return (0 != q.value(0).toULongLong());
+}
+
+void MyMoneyStorageSql::readSchedules(void) {
+
+ TRY
+ m_storage->loadSchedules(fetchSchedules());
+ readFileInfo();
+ m_storage->loadScheduleId(m_hiIdSchedules);
+ PASS
+}
+
+const QMap<QString, MyMoneySchedule> MyMoneyStorageSql::fetchSchedules (const QStringList& idList, bool forUpdate) const {
+ DBG("*** Entering MyMoneyStorageSql::readSchedules");
+ signalProgress(0, m_schedules, QObject::tr("Loading schedules..."));
+ int progress = 0;
+ const MyMoneyDbTable& t = m_db.m_tables["kmmSchedules"];
+ MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
+ QMap<QString, MyMoneySchedule> sList;
+ //unsigned long lastId = 0;
+ const MyMoneyDbTable& ts = m_db.m_tables["kmmSplits"];
+ MyMoneySqlQuery qs(const_cast <MyMoneyStorageSql*> (this));
+ qs.prepare (ts.selectAllString(false) + " WHERE transactionId = :id ORDER BY splitId;");
+ MyMoneySqlQuery sq(const_cast <MyMoneyStorageSql*> (this));
+ sq.prepare ("SELECT payDate from kmmSchedulePaymentHistory where schedId = :id");
+
+ QString queryString (t.selectAllString(false));
+
+ // Use bind variables, instead of just inserting the values in the queryString,
+ // so that values containing a ':' will work.
+ if (! idList.empty()) {
+ queryString += " WHERE";
+ for (unsigned i = 0; i < idList.count(); ++i)
+ queryString += " id = :id" + QString::number(i) + " OR";
+ queryString = queryString.left(queryString.length() - 2);
+ }
+ queryString += " ORDER BY id;";
+
+ if (forUpdate)
+ queryString += " FOR UPDATE";
+
+ queryString += ";";
+
+ q.prepare (queryString);
+
+ if (! idList.empty()) {
+ QStringList::const_iterator bindVal = idList.begin();
+ for (int i = 0; bindVal != idList.end(); ++i, ++bindVal) {
+ q.bindValue (":id" + QString::number(i), *bindVal);
+ }
+ }
+
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("reading Schedules")));
+ while (q.next()) {
+ MyMoneyDbTable::field_iterator ft = t.begin();
+ int i = 0;
+ MyMoneySchedule s;
+ QString sId;
+ QString boolChar;
+ QDate nextPaymentDue;
+ while (ft != t.end()) {
+ CASE(id) sId = GETCSTRING;
+ else CASE(name) s.setName (GETSTRING);
+ else CASE(type) s.setType (static_cast<MyMoneySchedule::typeE>(GETINT));
+ else CASE(occurence) s.setOccurencePeriod (static_cast<MyMoneySchedule::occurenceE>(GETINT));
+ else CASE(occurenceMultiplier) s.setOccurenceMultiplier (GETINT);
+ else CASE(paymentType) s.setPaymentType (static_cast<MyMoneySchedule::paymentTypeE>(GETINT));
+ else CASE(startDate) s.setStartDate (GETDATE);
+ else CASE(endDate) s.setEndDate (GETDATE);
+ else CASE(fixed) {boolChar = GETSTRING; s.setFixed (boolChar == "Y");}
+ else CASE(autoEnter) {boolChar = GETSTRING; s.setAutoEnter (boolChar == "Y");}
+ else CASE(lastPayment) s.setLastPayment (GETDATE);
+ else CASE(weekendOption)
+ s.setWeekendOption (static_cast<MyMoneySchedule::weekendOptionE>(GETINT));
+ else CASE(nextPaymentDue) nextPaymentDue = GETDATE;
+ ++ft; ++i;
+ }
+ // convert simple occurence to compound occurence
+ int mult = s.occurenceMultiplier();
+ MyMoneySchedule::occurenceE occ = s.occurencePeriod();
+ MyMoneySchedule::simpleToCompoundOccurence(mult,occ);
+ s.setOccurencePeriod(occ);
+ s.setOccurenceMultiplier(mult);
+ // now assign the id to the schedule
+ MyMoneySchedule _s(sId, s);
+ s = _s;
+ // read the associated transaction
+// m_payeeList.clear();
+ const MyMoneyDbTable& t = m_db.m_tables["kmmTransactions"];
+ MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
+ q.prepare (t.selectAllString(false) + " WHERE id = :id;");
+ q.bindValue(":id", s.id());
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("reading Scheduled Transaction")));
+ if (!q.next()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("retrieving scheduled transaction")));
+ MyMoneyTransaction tx(s.id(), MyMoneyTransaction());
+ ft = t.begin();
+ i = 0;
+ while (ft != t.end()) {
+ CASE(postDate) tx.setPostDate(GETDATE);
+ else CASE(memo) tx.setMemo(GETSTRING);
+ else CASE(entryDate) tx.setEntryDate(GETDATE);
+ else CASE(currencyId) tx.setCommodity(GETCSTRING);
+ else CASE(bankId) tx.setBankID(GETSTRING);
+ ++ft; ++i;
+ }
+
+ qs.bindValue(":id", s.id());
+ if (!qs.exec()) throw new MYMONEYEXCEPTION(buildError (qs, __func__, "reading Scheduled Splits"));
+ while (qs.next()) {
+ MyMoneySplit sp;
+ readSplit (sp, qs, ts);
+ tx.addSplit (sp);
+ }
+// if (!m_payeeList.isEmpty())
+// readPayees(m_payeeList);
+ // Process any key value pair
+ tx.setPairs(readKeyValuePairs("TRANSACTION", s.id()).pairs());
+
+ // If the transaction doesn't have a post date, setTransaction will reject it.
+ // The old way of handling things was to store the next post date in the schedule object
+ // and set the transaction post date to QDate().
+ // For compatibility, if this is the case, copy the next post date from the schedule object
+ // to the transaction object post date.
+ if (!tx.postDate().isValid()) {
+ tx.setPostDate(nextPaymentDue);
+ }
+
+ s.setTransaction(tx);
+
+ // read in the recorded payments
+ sq.bindValue(":id", s.id());
+ if (!sq.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("reading schedule payment history")));
+ while (sq.next()) s.recordPayment (sq.value(0).toDate());
+
+ sList[s.id()] = s;
+
+ //FIXME: enable when schedules have KVPs.
+ // s.setPairs(readKeyValuePairs("SCHEDULE", s.id()).pairs());
+
+ //unsigned long id = extractId(s.id().data());
+ //if(id > lastId)
+ // lastId = id;
+
+ signalProgress(++progress, 0);
+ }
+ return sList;
+}
+
+void MyMoneyStorageSql::readSecurities(void) {
+ TRY
+ m_storage->loadSecurities(fetchSecurities());
+ readFileInfo();
+ m_storage->loadSecurityId(m_hiIdSecurities);
+ PASS
+}
+
+const QMap<QString, MyMoneySecurity> MyMoneyStorageSql::fetchSecurities (const QStringList& /*idList*/, bool /*forUpdate*/) const {
+ DBG("*** Entering MyMoneyStorageSql::readSecurities");
+ signalProgress(0, m_securities, QObject::tr("Loading securities..."));
+ int progress = 0;
+ QMap<QString, MyMoneySecurity> sList;
+ unsigned long lastId = 0;
+ const MyMoneyDbTable& t = m_db.m_tables["kmmSecurities"];
+ MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
+ q.prepare (t.selectAllString(false) + " ORDER BY id;");
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("reading Securities")));
+ while (q.next()) {
+ MyMoneyDbTable::field_iterator ft = t.begin();
+ int i = 0;
+ MyMoneySecurity e;
+ QString eid;
+ int saf = 0;
+ while (ft != t.end()) {
+ CASE(id) eid = GETSTRING;
+ else CASE(name) e.setName(GETSTRING);
+ else CASE(symbol) e.setTradingSymbol(GETSTRING);
+ else CASE(type) e.setSecurityType(static_cast<MyMoneySecurity::eSECURITYTYPE>(GETINT));
+ else CASE(smallestAccountFraction) saf = GETINT;
+ else CASE(tradingCurrency) e.setTradingCurrency(GETCSTRING);
+ else CASE(tradingMarket) e.setTradingMarket(GETSTRING);
+ ++ft; ++i;
+ }
+ if(e.tradingCurrency().isEmpty())
+ e.setTradingCurrency(m_storage->pairs()["kmm-baseCurrency"]);
+ if(saf == 0)
+ saf = 100;
+ e.setSmallestAccountFraction(saf);
+
+ // Process any key value pairs
+ e.setPairs(readKeyValuePairs("SECURITY", eid).pairs());
+ //tell the storage objects we have a new security object.
+
+ // FIXME: Adapt to new interface make sure, to take care of the currencies as well
+ // see MyMoneyStorageXML::readSecurites()
+ MyMoneySecurity security(eid,e);
+ sList[security.id()] = security;
+
+ unsigned long id = extractId(security.id());
+ if(id > lastId)
+ lastId = id;
+
+ signalProgress(++progress, 0);
+ }
+ return sList;
+}
+
+void MyMoneyStorageSql::readPrices(void) {
+
+ TRY
+// m_storage->addPrice(MyMoneyPrice(from, to, date, rate, source));
+ PASS
+
+}
+
+const MyMoneyPrice MyMoneyStorageSql::fetchSinglePrice (const QString& fromIdList, const QString& toIdList, const QDate& date_, bool exactDate, bool /*forUpdate*/) const {
+ DBG("*** Entering MyMoneyStorageSql::fetchSinglePrice");
+ const MyMoneyDbTable& t = m_db.m_tables["kmmPrices"];
+ MyMoneyDbTable::field_iterator tableEnd = t.end();
+ MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
+ QString queryString = t.selectAllString(false);
+
+ // Use bind variables, instead of just inserting the values in the queryString,
+ // so that values containing a ':' will work.
+ // See balance query for why the date logic seems odd.
+ queryString += " WHERE fromId = :fromId AND toId = :toId AND priceDate < :priceDate ";
+ if (exactDate)
+ queryString += "AND priceDate > :exactDate ";
+
+ queryString += "ORDER BY priceDate DESC;";
+
+ q.prepare(queryString);
+
+ QDate date (date_);
+
+ if(!date.isValid())
+ date = QDate::currentDate();
+
+ q.bindValue(":fromId", fromIdList);
+ q.bindValue(":toId", toIdList);
+ q.bindValue(":priceDate", date.addDays(1).toString(Qt::ISODate));
+
+ if (exactDate)
+ q.bindValue(":exactDate", date.toString(Qt::ISODate));
+
+ if (! q.exec()) {}
+
+ if (q.next()) {
+ MyMoneyDbTable::field_iterator ft = t.begin();
+ int i = 0;
+ QString from;
+ QString to;
+ QDate date;
+ MyMoneyMoney rate;
+ QString source;
+ bool foundFromId = false;
+ bool foundToId = false;
+ bool foundPriceDate = false;
+ bool foundPrice = false;
+ bool foundPriceSource = false;
+ while (ft != tableEnd) {
+ bool foundSomething = false;
+ if (!foundFromId && !foundSomething) {
+ CASE(fromId) {from = GETCSTRING; foundFromId = true; foundSomething = true;}
+ }
+ if (!foundToId && !foundSomething) {
+ CASE(toId) {to = GETCSTRING; foundToId = true; foundSomething = true;}
+ }
+ if (!foundPriceDate && !foundSomething) {
+ CASE(priceDate) {date = GETDATE; foundPriceDate = true; foundSomething = true;}
+ }
+ if (!foundPrice && !foundSomething) {
+ CASE(price) {rate = GETSTRING; foundPrice = true; foundSomething = true;}
+ }
+ if (!foundPriceSource && !foundSomething) {
+ CASE(priceSource) {source = GETSTRING; foundPriceSource = true; foundSomething = true;}
+ }
+ ++ft; ++i;
+ }
+
+ return MyMoneyPrice(fromIdList, toIdList, date, rate, source);
+ }
+
+ return MyMoneyPrice();
+}
+
+const MyMoneyPriceList MyMoneyStorageSql::fetchPrices (const QStringList& fromIdList, const QStringList& toIdList, bool forUpdate) const {
+ DBG("*** Entering MyMoneyStorageSql::readPrices");
+ signalProgress(0, m_prices, QObject::tr("Loading prices..."));
+ int progress = 0;
+ const_cast <MyMoneyStorageSql*> (this)->m_readingPrices = true;
+ MyMoneyPriceList pList;
+ const MyMoneyDbTable& t = m_db.m_tables["kmmPrices"];
+ MyMoneyDbTable::field_iterator tableEnd = t.end();
+ MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
+ QString queryString = t.selectAllString(false);
+
+ // Use bind variables, instead of just inserting the values in the queryString,
+ // so that values containing a ':' will work.
+ if (! fromIdList.empty()) {
+ queryString += " WHERE (";
+ for (unsigned i = 0; i < fromIdList.count(); ++i) {
+ queryString += " fromId = :fromId" + QString::number(i) + " OR";
+ }
+ queryString = queryString.left(queryString.length() - 2) + ")";
+ }
+ if (! toIdList.empty()) {
+ queryString += " AND (";
+ for (unsigned i = 0; i < toIdList.count(); ++i) {
+ queryString += " toId = :toId" + QString::number(i) + " OR";
+ }
+ queryString = queryString.left(queryString.length() - 2) + ")";
+ }
+
+
+ if (forUpdate)
+ queryString += " FOR UPDATE";
+
+ queryString += ";";
+
+ q.prepare (queryString);
+
+ if (! fromIdList.empty()) {
+ QStringList::const_iterator bindVal = fromIdList.begin();
+ for (int i = 0; bindVal != fromIdList.end(); ++i, ++bindVal) {
+ q.bindValue (":fromId" + QString::number(i), *bindVal);
+ }
+ }
+ if (! toIdList.empty()) {
+ QStringList::const_iterator bindVal = toIdList.begin();
+ for (int i = 0; bindVal != toIdList.end(); ++i, ++bindVal) {
+ q.bindValue (":toId" + QString::number(i), *bindVal);
+ }
+ }
+
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("reading Prices")));
+ while (q.next()) {
+ MyMoneyDbTable::field_iterator ft = t.begin();
+ int i = 0;
+ QString from;
+ QString to;
+ QDate date;
+ MyMoneyMoney rate;
+ QString source;
+
+ while (ft != tableEnd) {
+ CASE(fromId) from = GETCSTRING;
+ else CASE(toId) to = GETCSTRING;
+ else CASE(priceDate) date = GETDATE;
+ else CASE(price) rate = GETSTRING;
+ else CASE(priceSource) source = GETSTRING;
+ ++ft; ++i;
+ }
+ pList [MyMoneySecurityPair(from, to)].insert(date, MyMoneyPrice(from, to, date, rate, source));
+ signalProgress(++progress, 0);
+ }
+ const_cast <MyMoneyStorageSql*> (this)->m_readingPrices = false;
+
+ return pList;
+}
+
+void MyMoneyStorageSql::readCurrencies(void) {
+ TRY
+ m_storage->loadCurrencies(fetchCurrencies());
+ PASS
+}
+
+const QMap<QString, MyMoneySecurity> MyMoneyStorageSql::fetchCurrencies (const QStringList& idList, bool forUpdate) const {
+ DBG("*** Entering MyMoneyStorageSql::readCurrencies");
+ signalProgress(0, m_currencies, QObject::tr("Loading currencies..."));
+ int progress = 0;
+ QMap<QString, MyMoneySecurity> cList;
+ const MyMoneyDbTable& t = m_db.m_tables["kmmCurrencies"];
+ MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
+
+ QString queryString (t.selectAllString(false));
+
+ // Use bind variables, instead of just inserting the values in the queryString,
+ // so that values containing a ':' will work.
+ if (! idList.empty()) {
+ queryString += " WHERE";
+ for (unsigned i = 0; i < idList.count(); ++i)
+ queryString += " isocode = :id" + QString::number(i) + " OR";
+ queryString = queryString.left(queryString.length() - 2);
+ }
+
+ queryString += " ORDER BY ISOcode";
+
+ if (forUpdate)
+ queryString += " FOR UPDATE";
+
+ queryString += ";";
+
+ q.prepare (queryString);
+
+ if (! idList.empty()) {
+ QStringList::const_iterator bindVal = idList.begin();
+ for (int i = 0; bindVal != idList.end(); ++i, ++bindVal) {
+ q.bindValue (":id" + QString::number(i), *bindVal);
+ }
+ }
+
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("reading Currencies")));
+ while (q.next()) {
+ MyMoneyDbTable::field_iterator ft = t.begin();
+ int i = 0;
+ QString id;
+ MyMoneySecurity c;
+ QChar symbol[3];
+ while (ft != t.end()) {
+ CASE(ISOcode) id = GETCSTRING;
+ else CASE(name) c.setName(GETSTRING);
+ else CASE(type) c.setSecurityType(static_cast<MyMoneySecurity::eSECURITYTYPE>(GETINT));
+ else CASE(symbol1) symbol[0] = QChar(GETINT);
+ else CASE(symbol2) symbol[1] = QChar(GETINT);
+ else CASE(symbol3) symbol[2] = QChar(GETINT);
+ else CASE(partsPerUnit) c.setPartsPerUnit(GETINT);
+ else CASE(smallestCashFraction) c.setSmallestCashFraction(GETINT);
+ else CASE(smallestAccountFraction) c.setSmallestAccountFraction(GETINT);
+ ++ft; ++i;
+ }
+ c.setTradingSymbol(QString(symbol, 3).stripWhiteSpace());
+
+ cList[id] = MyMoneySecurity(id, c);
+
+ signalProgress(++progress, 0);
+ }
+ return cList;
+}
+
+void MyMoneyStorageSql::readReports(void) {
+ TRY
+ m_storage->loadReports(fetchReports());
+ readFileInfo();
+ m_storage->loadReportId(m_hiIdReports);
+ PASS
+}
+
+const QMap<QString, MyMoneyReport> MyMoneyStorageSql::fetchReports (const QStringList& /*idList*/, bool /*forUpdate*/) const {
+ DBG("*** Entering MyMoneyStorageSql::readReports");
+ signalProgress(0, m_reports, QObject::tr("Loading reports..."));
+ int progress = 0;
+ const MyMoneyDbTable& t = m_db.m_tables["kmmReportConfig"];
+ MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
+ q.prepare (t.selectAllString(true));
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("reading reports")));
+ QMap<QString, MyMoneyReport> rList;
+ while (q.next()) {
+ MyMoneyDbTable::field_iterator ft = t.begin();
+ int i = 0;
+ QDomDocument d;
+ while (ft != t.end()) {
+ CASE(XML) d.setContent(GETSTRING, false);
+ ++ft; ++i;
+ }
+ QDomNode child = d.firstChild();
+ child = child.firstChild();
+ MyMoneyReport report;
+
+ if (report.read(child.toElement()))
+ rList[report.id()] = report;
+
+ signalProgress(++progress, 0);
+ }
+ return rList;
+}
+
+const QMap<QString, MyMoneyBudget> MyMoneyStorageSql::fetchBudgets (const QStringList& idList, bool forUpdate) const {
+ DBG("*** Entering MyMoneyStorageSql::readBudgets");
+ signalProgress(0, m_budgets, QObject::tr("Loading budgets..."));
+ int progress = 0;
+ const MyMoneyDbTable& t = m_db.m_tables["kmmBudgetConfig"];
+ MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
+ QString queryString (t.selectAllString(false));
+ if (! idList.empty()) {
+ queryString += " WHERE id = '" + idList.join("' OR id = '") + "'";
+ }
+ if (forUpdate)
+ queryString += " FOR UPDATE";
+
+ queryString += ";";
+
+ q.prepare (queryString);
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("reading budgets")));
+ QMap<QString, MyMoneyBudget> budgets;
+ while (q.next()) {
+ MyMoneyDbTable::field_iterator ft = t.begin();
+ int i = 0;
+ QDomDocument d;
+ while (ft != t.end()) {
+ CASE(XML) d.setContent(GETSTRING, false);
+ ++ft; ++i;
+ }
+ QDomNode child = d.firstChild();
+ child = child.firstChild();
+ MyMoneyBudget budget (child.toElement());
+ budgets.insert(budget.id(), budget);
+ signalProgress(++progress, 0);
+ }
+ return budgets;
+}
+
+void MyMoneyStorageSql::readBudgets(void) {
+ m_storage->loadBudgets(fetchBudgets());
+}
+
+const MyMoneyKeyValueContainer MyMoneyStorageSql::readKeyValuePairs (const QString& kvpType, const QString& kvpId) const {
+ DBG("*** Entering MyMoneyStorageSql::readKeyValuePairs");
+ MyMoneyKeyValueContainer list;
+ MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
+ q.prepare ("SELECT kvpKey, kvpData from kmmKeyValuePairs where kvpType = :type and kvpId = :id;");
+ q.bindValue(":type", kvpType);
+ q.bindValue(":id", kvpId);
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("reading Kvp for %1 %2").arg(kvpType)
+ .arg(kvpId)));
+ while (q.next()) list.setValue(q.value(0).toString(), q.value(1).toString());
+ return (list);
+}
+
+const QMap<QString, MyMoneyKeyValueContainer> MyMoneyStorageSql::readKeyValuePairs (const QString& kvpType, const QStringList& kvpIdList) const {
+ DBG("*** Entering MyMoneyStorageSql::readKeyValuePairs");
+ QMap<QString, MyMoneyKeyValueContainer> retval;
+
+ MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
+ QString query ("SELECT kvpId, kvpKey, kvpData from kmmKeyValuePairs where kvpType = :type");
+
+ if (!kvpIdList.empty()) {
+ query += " and kvpId IN ('" + kvpIdList.join("', '") + "')";
+ }
+
+ query += " order by kvpId;";
+ q.prepare (query);
+ q.bindValue(":type", kvpType);
+ if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, QString("reading Kvp List for %1").arg(kvpType)));
+ while (q.next()) {
+ retval [q.value(0).toString()].setValue(q.value(1).toString(), q.value(2).toString());
+ }
+
+ return (retval);
+}
+
+long unsigned MyMoneyStorageSql::getNextBudgetId() const {
+ const_cast <MyMoneyStorageSql*> (this)->readFileInfo();
+ return m_hiIdBudgets;
+}
+
+long unsigned MyMoneyStorageSql::getNextAccountId() const {
+ const_cast <MyMoneyStorageSql*> (this)->readFileInfo();
+ return m_hiIdAccounts;
+}
+
+long unsigned MyMoneyStorageSql::getNextInstitutionId() const {
+ const_cast <MyMoneyStorageSql*> (this)->readFileInfo();
+ return m_hiIdInstitutions;
+}
+
+long unsigned MyMoneyStorageSql::getNextPayeeId() const {
+ const_cast <MyMoneyStorageSql*> (this)->readFileInfo();
+ return m_hiIdPayees;
+}
+
+long unsigned MyMoneyStorageSql::getNextReportId() const {
+ const_cast <MyMoneyStorageSql*> (this)->readFileInfo();
+ return m_hiIdReports;
+}
+
+long unsigned MyMoneyStorageSql::getNextScheduleId() const {
+ const_cast <MyMoneyStorageSql*> (this)->readFileInfo();
+ return m_hiIdSchedules;
+}
+
+long unsigned MyMoneyStorageSql::getNextSecurityId() const {
+ const_cast <MyMoneyStorageSql*> (this)->readFileInfo();
+ return m_hiIdSecurities;
+}
+
+long unsigned MyMoneyStorageSql::getNextTransactionId() const {
+ const_cast <MyMoneyStorageSql*> (this)->readFileInfo();
+ return m_hiIdTransactions;
+}
+
+long unsigned MyMoneyStorageSql::incrementBudgetId() {
+ MyMoneySqlQuery q(this);
+
+ startCommitUnit (__func__);
+ q.prepare("SELECT hiBudgetId FROM kmmFileInfo FOR UPDATE");
+ q.exec();
+ q.next();
+ long unsigned returnValue = (unsigned long) q.value(0).toULongLong();
+ ++returnValue;
+ q.prepare("UPDATE kmmFileInfo SET hiBudgetId = " + QString::number(returnValue));
+ q.exec();
+ endCommitUnit (__func__);
+ m_hiIdBudgets = returnValue;
+ return returnValue;
+}
+
+long unsigned MyMoneyStorageSql::incrementAccountId() {
+ MyMoneySqlQuery q(this);
+
+ startCommitUnit (__func__);
+ q.prepare("SELECT hiAccountId FROM kmmFileInfo FOR UPDATE");
+ q.exec();
+ q.next();
+ long unsigned returnValue = (unsigned long) q.value(0).toULongLong();
+ ++returnValue;
+ q.prepare("UPDATE kmmFileInfo SET hiAccountId = " + QString::number(returnValue));
+ q.exec();
+ endCommitUnit (__func__);
+ m_hiIdAccounts = returnValue;
+ return returnValue;
+}
+
+long unsigned MyMoneyStorageSql::incrementInstitutionId() {
+ MyMoneySqlQuery q(this);
+
+ startCommitUnit (__func__);
+ q.prepare("SELECT hiInstitutionId FROM kmmFileInfo FOR UPDATE");
+ q.exec();
+ q.next();
+ long unsigned returnValue = (unsigned long) q.value(0).toULongLong();
+ ++returnValue;
+ q.prepare("UPDATE kmmFileInfo SET hiInstitutionId = " + QString::number(returnValue));
+ q.exec();
+ endCommitUnit (__func__);
+ m_hiIdInstitutions = returnValue;
+ return returnValue;
+}
+
+long unsigned MyMoneyStorageSql::incrementPayeeId() {
+ MyMoneySqlQuery q(this);
+
+ startCommitUnit (__func__);
+ q.prepare("SELECT hiPayeeId FROM kmmFileInfo FOR UPDATE");
+ q.exec();
+ q.next();
+ long unsigned returnValue = (unsigned long) q.value(0).toULongLong();
+ ++returnValue;
+ q.prepare("UPDATE kmmFileInfo SET hiPayeeId = " + QString::number(returnValue));
+ q.exec();
+ endCommitUnit (__func__);
+ m_hiIdPayees = returnValue;
+ return returnValue;
+}
+
+long unsigned MyMoneyStorageSql::incrementReportId() {
+ MyMoneySqlQuery q(this);
+
+ startCommitUnit (__func__);
+ q.prepare("SELECT hiReportId FROM kmmFileInfo FOR UPDATE");
+ q.exec();
+ q.next();
+ long unsigned returnValue = (unsigned long) q.value(0).toULongLong();
+ ++returnValue;
+ q.prepare("UPDATE kmmFileInfo SET hiReportId = " + QString::number(returnValue));
+ q.exec();
+ endCommitUnit (__func__);
+ m_hiIdReports = returnValue;
+ return returnValue;
+}
+
+long unsigned MyMoneyStorageSql::incrementScheduleId() {
+ MyMoneySqlQuery q(this);
+
+ startCommitUnit (__func__);
+ q.prepare("SELECT hiScheduleId FROM kmmFileInfo FOR UPDATE");
+ q.exec();
+ q.next();
+ long unsigned returnValue = (unsigned long) q.value(0).toULongLong();
+ ++returnValue;
+ q.prepare("UPDATE kmmFileInfo SET hiScheduleId = " + QString::number(returnValue));
+ q.exec();
+ endCommitUnit (__func__);
+ m_hiIdSchedules = returnValue;
+ return returnValue;
+}
+
+long unsigned MyMoneyStorageSql::incrementSecurityId() {
+ MyMoneySqlQuery q(this);
+
+ startCommitUnit (__func__);
+ q.prepare("SELECT hiSecurityId FROM kmmFileInfo FOR UPDATE");
+ q.exec();
+ q.next();
+ long unsigned returnValue = (unsigned long) q.value(0).toULongLong();
+ ++returnValue;
+ q.prepare("UPDATE kmmFileInfo SET hiSecurityId = " + QString::number(returnValue));
+ q.exec();
+ endCommitUnit (__func__);
+ m_hiIdSecurities = returnValue;
+ return returnValue;
+}
+
+long unsigned MyMoneyStorageSql::incrementTransactionId() {
+ MyMoneySqlQuery q(this);
+
+ startCommitUnit (__func__);
+ q.prepare("SELECT hiTransactionId FROM kmmFileInfo FOR UPDATE");
+ q.exec();
+ q.next();
+ long unsigned returnValue = (unsigned long) q.value(0).toULongLong();
+ ++returnValue;
+ q.prepare("UPDATE kmmFileInfo SET hiTransactionId = " + QString::number(returnValue));
+ q.exec();
+ endCommitUnit (__func__);
+ m_hiIdTransactions = returnValue;
+ return returnValue;
+}
+
+void MyMoneyStorageSql::loadAccountId(const unsigned long& id)
+{
+ m_hiIdAccounts = id;
+ writeFileInfo();
+}
+
+void MyMoneyStorageSql::loadTransactionId(const unsigned long& id)
+{
+ m_hiIdTransactions = id;
+ writeFileInfo();
+}
+
+void MyMoneyStorageSql::loadPayeeId(const unsigned long& id)
+{
+ m_hiIdPayees = id;
+ writeFileInfo();
+}
+
+void MyMoneyStorageSql::loadInstitutionId(const unsigned long& id)
+{
+ m_hiIdInstitutions = id;
+ writeFileInfo();
+}
+
+void MyMoneyStorageSql::loadScheduleId(const unsigned long& id)
+{
+ m_hiIdSchedules = id;
+ writeFileInfo();
+}
+
+void MyMoneyStorageSql::loadSecurityId(const unsigned long& id)
+{
+ m_hiIdSecurities = id;
+ writeFileInfo();
+}
+
+void MyMoneyStorageSql::loadReportId(const unsigned long& id)
+{
+ m_hiIdReports = id;
+ writeFileInfo();
+}
+
+void MyMoneyStorageSql::loadBudgetId(const unsigned long& id)
+{
+ m_hiIdBudgets = id;
+ writeFileInfo();
+}
+
+//****************************************************
+long unsigned MyMoneyStorageSql::calcHighId
+ (const long unsigned& i, const QString& id) {
+ DBG("*** Entering MyMoneyStorageSql::calcHighId");
+ QString nid = id;
+ long unsigned high = (unsigned long) nid.replace(QRegExp("[A-Z]*"), "").toULongLong();
+ return std::max(high, i);
+}
+
+void MyMoneyStorageSql::setProgressCallback(void(*callback)(int, int, const QString&)) {
+ m_progressCallback = callback;
+}
+
+void MyMoneyStorageSql::signalProgress(int current, int total, const QString& msg) const {
+ if (m_progressCallback != 0)
+ (*m_progressCallback)(current, total, msg);
+}
+
+// **************************** Error display routine *******************************
+QString& MyMoneyStorageSql::buildError (const QSqlQuery& q, const QString& function, const QString& message) const {
+ QString s = QString("Error in function %1 : %2").arg(function).arg(message);
+ QSqlError e = lastError();
+ s += QString ("\nDriver = %1, Host = %2, User = %3, Database = %4")
+ .arg(driverName()).arg(hostName()).arg(userName()).arg(databaseName());
+ s += QString ("\nDriver Error: %1").arg(e.driverText());
+ s += QString ("\nDatabase Error No %1: %2").arg(e.number()).arg(e.databaseText());
+ e = q.lastError();
+ s += QString ("\nExecuted: %1").arg(q.executedQuery());
+ s += QString ("\nQuery error No %1: %2").arg(e.number()).arg(e.text());
+
+ const_cast <MyMoneyStorageSql*> (this)->m_error = s;
+ qDebug("%s", s.ascii());
+ const_cast <MyMoneyStorageSql*> (this)->cancelCommitUnit(function);
+ return (const_cast <MyMoneyStorageSql*> (this)->m_error);
+}
+
+// ************************* Build table descriptions ****************************
+MyMoneyDbDef::MyMoneyDbDef () {
+ FileInfo();
+ Institutions();
+ Payees();
+ Accounts();
+ Transactions();
+ Splits();
+ KeyValuePairs();
+ Schedules();
+ SchedulePaymentHistory();
+ Securities();
+ Prices();
+ Currencies();
+ Reports();
+ Budgets();
+ Balances();
+}
+
+/* PRIMARYKEY - these fields combine to form a unique key field on which the db will create an index
+ NOTNULL - this field should never be null
+ UNSIGNED - for numeric types, indicates the field is UNSIGNED
+ ?ISKEY - where there is no primary key, these fields can be used to uniquely identify a record
+ Default is that a field is not a part of a primary key, nullable, and if numeric, signed */
+
+#define PRIMARYKEY true
+#define NOTNULL true
+#define UNSIGNED false
+//#define ISKEY true
+
+void MyMoneyDbDef::FileInfo(void){
+ QValueList<KSharedPtr <MyMoneyDbColumn> > fields;
+ fields.append(new MyMoneyDbColumn("version", "varchar(16)"));
+ fields.append(new MyMoneyDbColumn("created", "date"));
+ fields.append(new MyMoneyDbColumn("lastModified", "date"));
+ fields.append(new MyMoneyDbColumn("baseCurrency", "char(3)"));
+ fields.append(new MyMoneyDbIntColumn("institutions", MyMoneyDbIntColumn::BIG, UNSIGNED));
+ fields.append(new MyMoneyDbIntColumn("accounts", MyMoneyDbIntColumn::BIG, UNSIGNED));
+ fields.append(new MyMoneyDbIntColumn("payees", MyMoneyDbIntColumn::BIG, UNSIGNED));
+ fields.append(new MyMoneyDbIntColumn("transactions", MyMoneyDbIntColumn::BIG, UNSIGNED));
+ fields.append(new MyMoneyDbIntColumn("splits", MyMoneyDbIntColumn::BIG, UNSIGNED));
+ fields.append(new MyMoneyDbIntColumn("securities", MyMoneyDbIntColumn::BIG, UNSIGNED));
+ fields.append(new MyMoneyDbIntColumn("prices", MyMoneyDbIntColumn::BIG, UNSIGNED));
+ fields.append(new MyMoneyDbIntColumn("currencies", MyMoneyDbIntColumn::BIG, UNSIGNED));
+ fields.append(new MyMoneyDbIntColumn("schedules", MyMoneyDbIntColumn::BIG, UNSIGNED));
+ fields.append(new MyMoneyDbIntColumn("reports", MyMoneyDbIntColumn::BIG, UNSIGNED));
+ fields.append(new MyMoneyDbIntColumn("kvps", MyMoneyDbIntColumn::BIG, UNSIGNED));
+ fields.append(new MyMoneyDbColumn("dateRangeStart", "date"));
+ fields.append(new MyMoneyDbColumn("dateRangeEnd", "date"));
+ fields.append(new MyMoneyDbIntColumn("hiInstitutionId", MyMoneyDbIntColumn::BIG, UNSIGNED));
+ fields.append(new MyMoneyDbIntColumn("hiPayeeId", MyMoneyDbIntColumn::BIG, UNSIGNED));
+ fields.append(new MyMoneyDbIntColumn("hiAccountId", MyMoneyDbIntColumn::BIG, UNSIGNED));
+ fields.append(new MyMoneyDbIntColumn("hiTransactionId", MyMoneyDbIntColumn::BIG, UNSIGNED));
+ fields.append(new MyMoneyDbIntColumn("hiScheduleId", MyMoneyDbIntColumn::BIG, UNSIGNED));
+ fields.append(new MyMoneyDbIntColumn("hiSecurityId", MyMoneyDbIntColumn::BIG, UNSIGNED));
+ fields.append(new MyMoneyDbIntColumn("hiReportId", MyMoneyDbIntColumn::BIG, UNSIGNED));
+ fields.append(new MyMoneyDbColumn("encryptData", "varchar(255)"));
+ fields.append(new MyMoneyDbColumn("updateInProgress", "char(1)"));
+ fields.append(new MyMoneyDbIntColumn("budgets", MyMoneyDbIntColumn::BIG, UNSIGNED));
+ fields.append(new MyMoneyDbIntColumn("hiBudgetId", MyMoneyDbIntColumn::BIG, UNSIGNED));
+ fields.append(new MyMoneyDbColumn("logonUser", "varchar(255)"));
+ fields.append(new MyMoneyDbDatetimeColumn("logonAt"));
+ fields.append(new MyMoneyDbIntColumn("fixLevel",
+ MyMoneyDbIntColumn::MEDIUM, UNSIGNED));
+ MyMoneyDbTable t("kmmFileInfo", fields);
+ t.buildSQLStrings();
+ m_tables[t.name()] = t;
+}
+
+void MyMoneyDbDef::Institutions(void){
+ QValueList<KSharedPtr <MyMoneyDbColumn> > fields;
+ fields.append(new MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL));
+ fields.append(new MyMoneyDbTextColumn("name", MyMoneyDbTextColumn::NORMAL, false, NOTNULL));
+ fields.append(new MyMoneyDbTextColumn("manager"));
+ fields.append(new MyMoneyDbTextColumn("routingCode"));
+ fields.append(new MyMoneyDbTextColumn("addressStreet"));
+ fields.append(new MyMoneyDbTextColumn("addressCity"));
+ fields.append(new MyMoneyDbTextColumn("addressZipcode"));
+ fields.append(new MyMoneyDbTextColumn("telephone"));
+ MyMoneyDbTable t("kmmInstitutions", fields);
+ t.buildSQLStrings();
+ m_tables[t.name()] = t;
+}
+
+void MyMoneyDbDef::Payees(void){
+ QValueList<KSharedPtr <MyMoneyDbColumn> > fields;
+ fields.append(new MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL));
+ fields.append(new MyMoneyDbTextColumn("name"));
+ fields.append(new MyMoneyDbTextColumn("reference"));
+ fields.append(new MyMoneyDbTextColumn("email"));
+ fields.append(new MyMoneyDbTextColumn("addressStreet"));
+ fields.append(new MyMoneyDbTextColumn("addressCity"));
+ fields.append(new MyMoneyDbTextColumn("addressZipcode"));
+ fields.append(new MyMoneyDbTextColumn("addressState"));
+ fields.append(new MyMoneyDbTextColumn("telephone"));
+ fields.append(new MyMoneyDbTextColumn("notes", MyMoneyDbTextColumn::LONG));
+ fields.append(new MyMoneyDbColumn("defaultAccountId", "varchar(32)"));
+ fields.append(new MyMoneyDbIntColumn("matchData", MyMoneyDbIntColumn::TINY, UNSIGNED));
+ fields.append(new MyMoneyDbColumn("matchIgnoreCase", "char(1)"));
+ fields.append(new MyMoneyDbTextColumn("matchKeys"));
+ MyMoneyDbTable t("kmmPayees", fields);
+ t.buildSQLStrings();
+ m_tables[t.name()] = t;
+}
+
+void MyMoneyDbDef::Accounts(void){
+ QValueList<KSharedPtr <MyMoneyDbColumn> > fields;
+ fields.append(new MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL));
+ fields.append(new MyMoneyDbColumn("institutionId", "varchar(32)"));
+ fields.append(new MyMoneyDbColumn("parentId", "varchar(32)"));
+ fields.append(new MyMoneyDbDatetimeColumn("lastReconciled"));
+ fields.append(new MyMoneyDbDatetimeColumn("lastModified"));
+ fields.append(new MyMoneyDbColumn("openingDate", "date"));
+ fields.append(new MyMoneyDbTextColumn("accountNumber"));
+ fields.append(new MyMoneyDbColumn("accountType", "varchar(16)", false, NOTNULL));
+ fields.append(new MyMoneyDbTextColumn("accountTypeString"));
+ fields.append(new MyMoneyDbColumn("isStockAccount", "char(1)"));
+ fields.append(new MyMoneyDbTextColumn("accountName"));
+ fields.append(new MyMoneyDbTextColumn("description"));
+ fields.append(new MyMoneyDbColumn("currencyId", "varchar(32)"));
+ fields.append(new MyMoneyDbTextColumn("balance"));
+ fields.append(new MyMoneyDbTextColumn("balanceFormatted"));
+ fields.append(new MyMoneyDbIntColumn("transactionCount", MyMoneyDbIntColumn::BIG, UNSIGNED));
+ MyMoneyDbTable t("kmmAccounts", fields);
+ t.buildSQLStrings();
+ m_tables[t.name()] = t;
+}
+
+void MyMoneyDbDef::Transactions(void){
+ QValueList<KSharedPtr <MyMoneyDbColumn> > fields;
+ fields.append(new MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL));
+ fields.append(new MyMoneyDbColumn("txType", "char(1)"));
+ fields.append(new MyMoneyDbDatetimeColumn("postDate"));
+ fields.append(new MyMoneyDbTextColumn("memo"));
+ fields.append(new MyMoneyDbDatetimeColumn("entryDate"));
+ fields.append(new MyMoneyDbColumn("currencyId", "char(3)"));
+ fields.append(new MyMoneyDbTextColumn("bankId"));
+ MyMoneyDbTable t("kmmTransactions", fields);
+ t.buildSQLStrings();
+ m_tables[t.name()] = t;
+}
+
+void MyMoneyDbDef::Splits(void){
+ QValueList<KSharedPtr <MyMoneyDbColumn> > fields;
+ fields.append(new MyMoneyDbColumn("transactionId", "varchar(32)", PRIMARYKEY, NOTNULL));
+ fields.append(new MyMoneyDbColumn("txType", "char(1)"));
+ fields.append(new MyMoneyDbIntColumn("splitId", MyMoneyDbIntColumn::SMALL, UNSIGNED, PRIMARYKEY, NOTNULL));
+ fields.append(new MyMoneyDbColumn("payeeId", "varchar(32)"));
+ fields.append(new MyMoneyDbDatetimeColumn("reconcileDate"));
+ fields.append(new MyMoneyDbColumn("action", "varchar(16)"));
+ fields.append(new MyMoneyDbColumn("reconcileFlag", "char(1)"));
+ fields.append(new MyMoneyDbTextColumn("value", MyMoneyDbTextColumn::NORMAL, false, NOTNULL));
+ fields.append(new MyMoneyDbColumn("valueFormatted", "text"));
+ fields.append(new MyMoneyDbTextColumn("shares", MyMoneyDbTextColumn::NORMAL, false, NOTNULL));
+ fields.append(new MyMoneyDbTextColumn("sharesFormatted"));
+ fields.append(new MyMoneyDbTextColumn("price", MyMoneyDbTextColumn::NORMAL, false));
+ fields.append(new MyMoneyDbTextColumn("priceFormatted"));
+ fields.append(new MyMoneyDbTextColumn("memo"));
+ fields.append(new MyMoneyDbColumn("accountId", "varchar(32)", false, NOTNULL));
+ fields.append(new MyMoneyDbColumn("checkNumber", "varchar(32)"));
+ fields.append(new MyMoneyDbDatetimeColumn("postDate"));
+ fields.append(new MyMoneyDbTextColumn("bankId"));
+ MyMoneyDbTable t("kmmSplits", fields);
+ QStringList list;
+ list << "accountId" << "txType";
+ t.addIndex("kmmSplitsaccount_type", list, false);
+ t.buildSQLStrings();
+ m_tables[t.name()] = t;
+}
+
+void MyMoneyDbDef::KeyValuePairs(void){
+ QValueList<KSharedPtr <MyMoneyDbColumn> > fields;
+ fields.append(new MyMoneyDbColumn("kvpType", "varchar(16)", false, NOTNULL));
+ fields.append(new MyMoneyDbColumn("kvpId", "varchar(32)"));
+ fields.append(new MyMoneyDbColumn("kvpKey", "varchar(255)", false, NOTNULL));
+ fields.append(new MyMoneyDbTextColumn("kvpData"));
+ MyMoneyDbTable t("kmmKeyValuePairs", fields);
+ QStringList list;
+ list << "kvpType" << "kvpId";
+ t.addIndex("type_id", list, false);
+ t.buildSQLStrings();
+ m_tables[t.name()] = t;
+}
+
+void MyMoneyDbDef::Schedules(void){
+ QValueList<KSharedPtr <MyMoneyDbColumn> > fields;
+ fields.append(new MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL));
+ fields.append(new MyMoneyDbTextColumn("name", MyMoneyDbTextColumn::NORMAL, false, NOTNULL));
+ fields.append(new MyMoneyDbIntColumn("type", MyMoneyDbIntColumn::TINY, UNSIGNED, false, NOTNULL));
+ fields.append(new MyMoneyDbTextColumn("typeString"));
+ fields.append(new MyMoneyDbIntColumn("occurence", MyMoneyDbIntColumn::SMALL, UNSIGNED, false,
+ NOTNULL));
+ fields.append(new MyMoneyDbIntColumn("occurenceMultiplier", MyMoneyDbIntColumn::SMALL, UNSIGNED,
+ false, NOTNULL));
+ fields.append(new MyMoneyDbTextColumn("occurenceString"));
+ fields.append(new MyMoneyDbIntColumn("paymentType", MyMoneyDbIntColumn::TINY, UNSIGNED));
+ fields.append(new MyMoneyDbTextColumn("paymentTypeString", MyMoneyDbTextColumn::LONG));
+ fields.append(new MyMoneyDbColumn("startDate", "date", false, NOTNULL));
+ fields.append(new MyMoneyDbColumn("endDate", "date"));
+ fields.append(new MyMoneyDbColumn("fixed", "char(1)", false, NOTNULL));
+ fields.append(new MyMoneyDbColumn("autoEnter", "char(1)", false, NOTNULL));
+ fields.append(new MyMoneyDbColumn("lastPayment", "date"));
+ fields.append(new MyMoneyDbColumn("nextPaymentDue", "date"));
+ fields.append(new MyMoneyDbIntColumn("weekendOption", MyMoneyDbIntColumn::TINY, UNSIGNED, false,
+ NOTNULL));
+ fields.append(new MyMoneyDbTextColumn("weekendOptionString"));
+ MyMoneyDbTable t("kmmSchedules", fields);
+ t.buildSQLStrings();
+ m_tables[t.name()] = t;
+}
+
+void MyMoneyDbDef::SchedulePaymentHistory(void){
+ QValueList<KSharedPtr <MyMoneyDbColumn> > fields;
+ fields.append(new MyMoneyDbColumn("schedId", "varchar(32)", PRIMARYKEY, NOTNULL));
+ fields.append(new MyMoneyDbColumn("payDate", "date", PRIMARYKEY, NOTNULL));
+ MyMoneyDbTable t("kmmSchedulePaymentHistory", fields);
+ t.buildSQLStrings();
+ m_tables[t.name()] = t;
+}
+
+void MyMoneyDbDef::Securities(void){
+ QValueList<KSharedPtr <MyMoneyDbColumn> > fields;
+ fields.append(new MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL));
+ fields.append(new MyMoneyDbColumn("name", "text", false, NOTNULL));
+ fields.append(new MyMoneyDbTextColumn("symbol"));
+ fields.append(new MyMoneyDbIntColumn("type", MyMoneyDbIntColumn::SMALL, UNSIGNED, false, NOTNULL));
+ fields.append(new MyMoneyDbTextColumn("typeString"));
+ fields.append(new MyMoneyDbColumn("smallestAccountFraction", "varchar(24)"));
+ fields.append(new MyMoneyDbTextColumn("tradingMarket"));
+ fields.append(new MyMoneyDbColumn("tradingCurrency", "char(3)"));
+ MyMoneyDbTable t("kmmSecurities", fields);
+ t.buildSQLStrings();
+ m_tables[t.name()] = t;
+}
+
+void MyMoneyDbDef::Prices(void){
+ QValueList<KSharedPtr <MyMoneyDbColumn> > fields;
+ fields.append(new MyMoneyDbColumn("fromId", "varchar(32)", PRIMARYKEY, NOTNULL));
+ fields.append(new MyMoneyDbColumn("toId", "varchar(32)", PRIMARYKEY, NOTNULL));
+ fields.append(new MyMoneyDbColumn("priceDate", "date", PRIMARYKEY, NOTNULL));
+ fields.append(new MyMoneyDbTextColumn("price", MyMoneyDbTextColumn::NORMAL, false, NOTNULL));
+ fields.append(new MyMoneyDbTextColumn("priceFormatted"));
+ fields.append(new MyMoneyDbTextColumn("priceSource"));
+ MyMoneyDbTable t("kmmPrices", fields);
+ t.buildSQLStrings();
+ m_tables[t.name()] = t;
+}
+
+void MyMoneyDbDef::Currencies(void){
+ QValueList<KSharedPtr <MyMoneyDbColumn> > fields;
+ fields.append(new MyMoneyDbColumn("ISOcode", "char(3)", PRIMARYKEY, NOTNULL));
+ fields.append(new MyMoneyDbTextColumn("name", MyMoneyDbTextColumn::NORMAL, false, NOTNULL));
+ fields.append(new MyMoneyDbIntColumn("type", MyMoneyDbIntColumn::SMALL, UNSIGNED));
+ fields.append(new MyMoneyDbTextColumn("typeString"));
+ fields.append(new MyMoneyDbIntColumn("symbol1", MyMoneyDbIntColumn::SMALL, UNSIGNED));
+ fields.append(new MyMoneyDbIntColumn("symbol2", MyMoneyDbIntColumn::SMALL, UNSIGNED));
+ fields.append(new MyMoneyDbIntColumn("symbol3", MyMoneyDbIntColumn::SMALL, UNSIGNED));
+ fields.append(new MyMoneyDbColumn("symbolString", "varchar(255)"));
+ fields.append(new MyMoneyDbColumn("partsPerUnit", "varchar(24)"));
+ fields.append(new MyMoneyDbColumn("smallestCashFraction", "varchar(24)"));
+ fields.append(new MyMoneyDbColumn("smallestAccountFraction", "varchar(24)"));
+ MyMoneyDbTable t("kmmCurrencies", fields);
+ t.buildSQLStrings();
+ m_tables[t.name()] = t;
+}
+
+void MyMoneyDbDef::Reports(void) {
+ QValueList<KSharedPtr <MyMoneyDbColumn> > fields;
+ fields.append(new MyMoneyDbColumn("name", "varchar(255)", false, NOTNULL));
+ fields.append(new MyMoneyDbTextColumn("XML", MyMoneyDbTextColumn::LONG));
+ fields.append(new MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL));
+ MyMoneyDbTable t("kmmReportConfig", fields);
+ t.buildSQLStrings();
+ m_tables[t.name()] = t;
+}
+
+void MyMoneyDbDef::Budgets(void){
+ QValueList<KSharedPtr <MyMoneyDbColumn> > fields;
+ fields.append(new MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL));
+ fields.append(new MyMoneyDbColumn("name", "text", false, NOTNULL));
+ fields.append(new MyMoneyDbColumn("start", "date", false, NOTNULL));
+ fields.append(new MyMoneyDbTextColumn("XML", MyMoneyDbTextColumn::LONG));
+ MyMoneyDbTable t("kmmBudgetConfig", fields);
+ t.buildSQLStrings();
+ m_tables[t.name()] = t;
+}
+
+void MyMoneyDbDef::Balances(void){
+ MyMoneyDbView v("kmmBalances", "CREATE VIEW kmmBalances AS "
+ "SELECT kmmAccounts.id AS id, kmmAccounts.currencyId, "
+ "kmmSplits.txType, kmmSplits.value, kmmSplits.shares, "
+ "kmmSplits.postDate AS balDate, "
+ "kmmTransactions.currencyId AS txCurrencyId "
+ "FROM kmmAccounts, kmmSplits, kmmTransactions "
+ "WHERE kmmSplits.txType = 'N' "
+ "AND kmmSplits.accountId = kmmAccounts.id "
+ "AND kmmSplits.transactionId = kmmTransactions.id;");
+ m_views[v.name()] = v;
+}
+
+// function to write create SQL to a stream
+const QString MyMoneyDbDef::generateSQL (const QString& driver) const {
+ QString retval;
+ databaseTypeE dbType = m_drivers.driverToType(driver);
+ table_iterator tt = tableBegin();
+ while (tt != tableEnd()) {
+ retval += (*tt).generateCreateSQL(dbType) + '\n';
+ ++tt;
+ }
+ view_iterator vt = viewBegin();
+ while (vt != viewEnd()) {
+ retval += (*vt).createString() + '\n';
+ ++vt;
+ }
+ retval += '\n';
+
+ MyMoneyDbTable fi = m_tables["kmmFileInfo"];
+ QString qs = fi.insertString();
+ MyMoneyDbTable::field_iterator fit;
+ for (fit = fi.begin(); fit != fi.end(); ++fit) {
+ QString toReplace = (*fit)->name();
+ toReplace.prepend(':');
+ QString replace = "NULL";
+ if ((*fit)->name() == "version")
+ replace = QString::number(m_currentVersion);
+ if ((*fit)->name() == "fixLevel")
+ replace = QString::number
+ (MyMoneyFile::instance()->storage()->currentFixVersion());
+ if ((*fit)->name() == "created")
+ replace = QDate::currentDate().toString(Qt::ISODate);
+ if ((*fit)->name() == "lastModified")
+ replace = QDate::currentDate().toString(Qt::ISODate);
+ if ((*fit)->name() == "updateInProgress")
+ replace = enclose("N");
+ qs.replace(toReplace, replace);
+ }
+ qs += "\n\n";
+ retval += qs;
+
+ qs = QString();
+ unsigned int i;
+ QValueList<MyMoneyAccount> stdList;
+ stdList.append (MyMoneyFile::instance()->asset());
+ stdList.append (MyMoneyFile::instance()->equity());
+ stdList.append (MyMoneyFile::instance()->expense());
+ stdList.append (MyMoneyFile::instance()->income());
+ stdList.append (MyMoneyFile::instance()->liability());
+ for (i = 0; i < stdList.count(); ++i) {
+ MyMoneyAccount* pac = &stdList[i];
+ MyMoneyDbTable ac = m_tables["kmmAccounts"];
+ qs = ac.insertString();
+ MyMoneyDbTable::field_iterator act;
+ // do the following in reverse so the 'formatted' fields are
+ // correctly handled.
+ // Hmm, how does one use a QValueListIterator in reverse
+ // It'll be okay in Qt4 with QListIterator
+ for (act = ac.end(), --act; act != ac.begin(); --act) {
+ QString toReplace = (*act)->name();
+ toReplace.prepend(':');
+ QString replace = "NULL";
+ if ((*act)->name() == "accountType")
+ replace = QString::number(pac->accountType());
+ if ((*act)->name() == "accountTypeString")
+ replace = enclose(pac->name());
+ if ((*act)->name() == "isStockAccount")
+ replace = enclose("N");
+ if ((*act)->name() == "accountName")
+ replace = enclose(pac->name());
+ qs.replace(toReplace, replace);
+ }
+ qs.replace (":id", enclose(pac->id())); // a real kludge
+ qs += "\n\n";
+ retval += qs;
+ }
+ return retval;
+}
+
+//*****************************************************************************
+
+void MyMoneyDbTable::addIndex(const QString& name, const QStringList& columns, bool unique) {
+ m_indices.push_back (MyMoneyDbIndex (m_name, name, columns, unique));
+}
+
+void MyMoneyDbTable::buildSQLStrings (void) {
+ // build fixed SQL strings for this table
+ // build the insert string with placeholders for each field
+ QString qs = QString("INSERT INTO %1 (").arg(name());
+ QString ws = ") VALUES (";
+ field_iterator ft = m_fields.begin();
+ while (ft != m_fields.end()) {
+ qs += QString("%1, ").arg((*ft)->name());
+ ws += QString(":%1, ").arg((*ft)->name());
+ ++ft;
+ }
+ qs = qs.left(qs.length() - 2);
+ ws = ws.left(ws.length() - 2);
+ m_insertString = qs + ws + ");";
+ // build a 'select all' string (select * is deprecated)
+ // don't terminate with semicolon coz we may want a where or order clause
+ m_selectAllString = "SELECT " + columnList() + " FROM " + name();;
+
+ // build an update string; key fields go in the where clause
+ qs = "UPDATE " + name() + " SET ";
+ ws = QString();
+ ft = m_fields.begin();
+ while (ft != m_fields.end()) {
+ if ((*ft)->isPrimaryKey()) {
+ if (!ws.isEmpty()) ws += " AND ";
+ ws += QString("%1 = :%2").arg((*ft)->name()).arg((*ft)->name());
+ } else {
+ qs += QString("%1 = :%2, ").arg((*ft)->name()).arg((*ft)->name());
+ }
+ ++ft;
+ }
+ qs = qs.left(qs.length() - 2);
+ if (!ws.isEmpty()) qs += " WHERE " + ws;
+ m_updateString = qs + ";";
+ // build a delete string; where clause as for update
+ qs = "DELETE FROM " + name();
+ if (!ws.isEmpty()) qs += " WHERE " + ws;
+ m_deleteString = qs + ";";
+ }
+
+const QString MyMoneyDbTable::columnList() const {
+ field_iterator ft = m_fields.begin();
+ QString qs;
+ ft = m_fields.begin();
+ while (ft != m_fields.end()) {
+ qs += QString("%1, ").arg((*ft)->name());
+ ++ft;
+ }
+ return (qs.left(qs.length() - 2));
+}
+
+const QString MyMoneyDbTable::generateCreateSQL (databaseTypeE dbType) const {
+ QString qs = QString("CREATE TABLE %1 (").arg(name());
+ QString pkey;
+ for (field_iterator it = m_fields.begin(); it != m_fields.end(); ++it) {
+ qs += (*it)->generateDDL (dbType) + ", ";
+ if ((*it)->isPrimaryKey ())
+ pkey += (*it)->name () + ", ";
+ }
+
+ if (!pkey.isEmpty()) {
+ qs += "PRIMARY KEY (" + pkey;
+ qs = qs.left(qs.length() -2) + "))";
+ } else {
+ qs = qs.left(qs.length() -2) + ")";
+ }
+
+ if (dbType == Mysql)
+ qs += " ENGINE = InnoDB;\n";
+ else
+ qs += ";\n";
+
+ for (index_iterator ii = m_indices.begin(); ii != m_indices.end(); ++ii) {
+ qs += (*ii).generateDDL(dbType);
+ }
+ return qs;
+}
+
+const QString MyMoneyDbTable::dropPrimaryKeyString(databaseTypeE dbType) const
+{
+ if (dbType == Mysql || dbType == Oracle8)
+ return "ALTER TABLE " + m_name + " DROP PRIMARY KEY;";
+ else if (dbType == Postgresql)
+ return "ALTER TABLE " + m_name + " DROP CONSTRAINT " + m_name + "_pkey;";
+ else if (dbType == Sqlite3)
+ return "";
+
+ return "";
+}
+
+const QString MyMoneyDbTable::modifyColumnString(databaseTypeE dbType, const QString& columnName, const MyMoneyDbColumn& newDef) const {
+ QString qs = "ALTER TABLE " + m_name + " ";
+ if (dbType == Mysql)
+ qs += "CHANGE " + columnName + " " + newDef.generateDDL(dbType);
+ else if (dbType == Postgresql)
+ qs += "ALTER COLUMN " + columnName + " TYPE " + newDef.generateDDL(dbType).section(' ', 1);
+ else if (dbType == Sqlite3)
+ qs = "";
+ else if (dbType == Oracle8)
+ qs = "MODIFY " + columnName + " " + newDef.generateDDL(dbType);
+
+ return qs;
+}
+
+//*****************************************************************************
+const QString MyMoneyDbIndex::generateDDL (databaseTypeE dbType) const
+{
+ Q_UNUSED(dbType);
+
+ QString qs = "CREATE ";
+
+ if (m_unique)
+ qs += "UNIQUE ";
+
+ qs += "INDEX " + m_table + "_" + m_name + "_idx ON "
+ + m_table + " (";
+
+ // The following should probably be revised. MySQL supports an index on
+ // partial columns, but not on a function. Postgres supports an index on
+ // the result of an SQL function, but not a partial column. There should be
+ // a way to merge these, and support other DBMSs like SQLite at the same time.
+ // For now, if we just use plain columns, this will work fine.
+ for (QStringList::const_iterator it = m_columns.begin(); it != m_columns.end(); ++it) {
+ qs += *it + ",";
+ }
+
+ qs = qs.left(qs.length() - 1) + ");\n";
+
+ return qs;
+}
+
+//*****************************************************************************
+// These are the actual column types.
+// TODO: consider changing all the else-if statements to driver classes.
+//
+
+MyMoneyDbColumn* MyMoneyDbColumn::clone () const
+{ return (new MyMoneyDbColumn (*this)); }
+
+MyMoneyDbIntColumn* MyMoneyDbIntColumn::clone () const
+{ return (new MyMoneyDbIntColumn (*this)); }
+
+MyMoneyDbDatetimeColumn* MyMoneyDbDatetimeColumn::clone () const
+{ return (new MyMoneyDbDatetimeColumn (*this)); }
+
+MyMoneyDbTextColumn* MyMoneyDbTextColumn::clone () const
+{ return (new MyMoneyDbTextColumn (*this)); }
+
+const QString MyMoneyDbColumn::generateDDL (databaseTypeE dbType) const
+{
+ Q_UNUSED(dbType);
+
+ QString qs = name() + " " + type();
+ if (isNotNull()) qs += " NOT NULL";
+ return qs;
+}
+
+const QString MyMoneyDbIntColumn::generateDDL (databaseTypeE dbType) const
+{
+ QString qs = name() + " ";
+
+ switch (m_type) {
+ case MyMoneyDbIntColumn::TINY:
+ if (dbType == Mysql || dbType == Sqlite3) {
+ qs += "tinyint ";
+ } else if (dbType == Postgresql) {
+ qs += "int2 ";
+ } else if (dbType == Db2) {
+ qs += "smallint ";
+ } else if (dbType == Oracle8) {
+ qs += "number(3) ";
+ } else {
+ // cross your fingers...
+ qs += "smallint ";
+ }
+ break;
+ case MyMoneyDbIntColumn::SMALL:
+ if (dbType == Mysql || dbType == Db2 || dbType == Sqlite3) {
+ qs += "smallint ";
+ } else if (dbType == Postgresql) {
+ qs += "int2 ";
+ } else if (dbType == Oracle8) {
+ qs += "number(5) ";
+ } else {
+ // cross your fingers...
+ qs += "smallint ";
+ }
+ break;
+ case MyMoneyDbIntColumn::MEDIUM:
+ if (dbType == Mysql || dbType == Db2) {
+ qs += "int ";
+ } else if (dbType == Postgresql) {
+ qs += "int4 ";
+ } else if (dbType == Sqlite3) {
+ qs += "integer ";
+ } else if (dbType == Oracle8) {
+ qs += "number(10) ";
+ } else {
+ // cross your fingers...
+ qs += "int ";
+ }
+ break;
+ case MyMoneyDbIntColumn::BIG:
+ if (dbType == Mysql || dbType == Db2 || dbType == Sqlite3) {
+ qs += "bigint ";
+ } else if (dbType == Postgresql) {
+ qs += "int8 ";
+ } else if (dbType == Oracle8) {
+ qs += "number(20) ";
+ } else {
+ // cross your fingers...
+ qs += "bigint ";
+ }
+ break;
+ default:
+ qs += "int ";
+ break;
+ }
+
+ if ((! m_isSigned) && (dbType == Mysql || dbType == Sqlite3)) {
+ qs += "unsigned ";
+ }
+
+ if (isNotNull()) qs += " NOT NULL";
+ if ((! m_isSigned) && (dbType == Postgresql)) {
+ qs += " check(" + name() + " >= 0)";
+ }
+ return qs;
+}
+
+const QString MyMoneyDbTextColumn::generateDDL (databaseTypeE dbType) const
+{
+ QString qs = name() + " ";
+
+ switch (m_type) {
+ case MyMoneyDbTextColumn::TINY:
+ if (dbType == Mysql || dbType == Sqlite3) {
+ qs += "tinytext ";
+ } else if (dbType == Postgresql) {
+ qs += "text ";
+ } else if (dbType == Db2) {
+ qs += "varchar(255) ";
+ } else if (dbType == Oracle8) {
+ qs += "varchar2(255) ";
+ } else {
+ // cross your fingers...
+ qs += "tinytext ";
+ }
+ break;
+ case MyMoneyDbTextColumn::NORMAL:
+ if (dbType == Mysql || dbType == Sqlite3 || dbType == Postgresql) {
+ qs += "text ";
+ } else if (dbType == Db2) {
+ qs += "clob(64K) ";
+ } else if (dbType == Oracle8) {
+ qs += "clob ";
+ } else {
+ // cross your fingers...
+ qs += "text ";
+ }
+ break;
+ case MyMoneyDbTextColumn::MEDIUM:
+ if (dbType == Mysql || dbType == Sqlite3 ) {
+ qs += "mediumtext ";
+ } else if (dbType == Postgresql) {
+ qs += "text ";
+ } else if (dbType == Db2) {
+ qs += "clob(16M) ";
+ } else if (dbType == Oracle8) {
+ qs += "clob ";
+ } else {
+ // cross your fingers...
+ qs += "mediumtext ";
+ }
+ break;
+ case MyMoneyDbTextColumn::LONG:
+ if (dbType == Mysql || dbType == Sqlite3 ) {
+ qs += "longtext ";
+ } else if (dbType == Postgresql) {
+ qs += "text ";
+ } else if (dbType == Db2) {
+ qs += "clob(2G) ";
+ } else if (dbType == Oracle8) {
+ qs += "clob ";
+ } else {
+ // cross your fingers...
+ qs += "longtext ";
+ }
+ break;
+ default:
+ if (dbType == Oracle8) {
+ qs += "clob ";
+ } else {
+ qs += "text ";
+ }
+ break;
+ }
+
+ if (isNotNull()) qs += " NOT NULL";
+
+ return qs;
+}
+
+const QString MyMoneyDbDatetimeColumn::generateDDL (databaseTypeE dbType) const
+{
+ QString qs = name() + " ";
+ if (dbType == Mysql || dbType == ODBC3) {
+ qs += "datetime ";
+ } else if (dbType == Postgresql || dbType == Db2 || dbType == Oracle8 || dbType == Sqlite3 ) {
+ qs += "timestamp ";
+ } else {
+ qs += "";
+ }
+ if (isNotNull()) qs += " NOT NULL";
+ return qs;
+}
diff --git a/kmymoney2/mymoney/storage/mymoneystoragesql.h b/kmymoney2/mymoney/storage/mymoneystoragesql.h
new file mode 100644
index 0000000..1abe70b
--- /dev/null
+++ b/kmymoney2/mymoney/storage/mymoneystoragesql.h
@@ -0,0 +1,807 @@
+/***************************************************************************
+ mymoneystoragesql.h
+ -------------------
+ begin : 11 November 2005
+ copyright : (C) 2005 by Tony Bloomfield
+ email : tonybloom@users.sourceforge.net
+ : Fernando Vilas <fvilas@iname.com>
+ ***************************************************************************/
+
+#ifndef MYMONEYSTORAGESQL_H
+#define MYMONEYSTORAGESQL_H
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qsqldatabase.h>
+#include <qsqlquery.h>
+#include <qsqlerror.h>
+#include <qvaluestack.h>
+
+class QIODevice;
+// ----------------------------------------------------------------------------
+// KDE Includes
+
+#include <kurl.h>
+#include <ksharedptr.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "imymoneystorageformat.h"
+#include "../mymoneyinstitution.h"
+#include "../mymoneypayee.h"
+#include "../mymoneyaccount.h"
+#include "../mymoneytransaction.h"
+#include "../mymoneysplit.h"
+#include "../mymoneyscheduled.h"
+#include "../mymoneysecurity.h"
+#include "../mymoneyprice.h"
+#include "../mymoneyreport.h"
+#include "../mymoneybudget.h"
+#include "../mymoneyfile.h"
+#include "../mymoneykeyvaluecontainer.h"
+#include "mymoneymap.h"
+#include "../mymoneymoney.h"
+#include "../mymoneytransactionfilter.h"
+
+// This is a convenience functor to make it easier to use STL algorithms
+// It will return false if the MyMoneyTransaction DOES match the filter.
+// This functor may disappear when all filtering can be handled in SQL.
+class FilterFail {
+ public:
+ FilterFail (const MyMoneyTransactionFilter& filter,
+ IMyMoneyStorage* storage)
+ : m_filter (filter),
+ m_storage (storage)
+ {}
+
+ inline bool operator() (const QPair<QString, MyMoneyTransaction>& transactionPair)
+ { return (*this) (transactionPair.second); }
+
+ inline bool operator() (const MyMoneyTransaction& transaction)
+ {
+ return (! m_filter.match(transaction)) && (m_filter.matchingSplits().count() == 0);
+ }
+
+ private:
+ MyMoneyTransactionFilter m_filter;
+ IMyMoneyStorage *m_storage;
+};
+
+/**
+@author Tony Bloomfield
+ */
+typedef enum databaseTypeE { // database (driver) type
+ Db2 = 0, //
+ Interbase, //
+ Mysql, //
+ Oracle8, //
+ ODBC3, //
+ Postgresql, //
+ Sqlite, //
+ Sybase, //
+ Sqlite3 //
+} _databaseType;
+
+class MyMoneyStorageSql;
+
+/**
+ * The MyMoneySqlQuery class is derived from QSqlQuery to provide
+ * a way to adjust some queries based on databaseTypeE and make
+ * debugging easier by providing a place to put debug statements.
+ */
+class MyMoneySqlQuery : public QSqlQuery {
+ public:
+ MyMoneySqlQuery (MyMoneyStorageSql* db = 0);
+ virtual ~MyMoneySqlQuery() {}
+ bool exec ();
+ bool prepare ( const QString & query );
+ private:
+ const MyMoneyStorageSql* m_db;
+};
+
+/**
+ * The MyMoneyDbDrivers class is a map from string to enum of db types.
+ */
+class MyMoneyDbDrivers {
+ public:
+ MyMoneyDbDrivers ();
+ /**
+ * @return a list ofsupported Qt database driver types, their qt names and useful names
+ **/
+ const QMap<QString, QString> driverMap() const {return (m_driverMap);};
+ databaseTypeE driverToType (const QString& driver) const;
+ bool isTested (databaseTypeE dbType) const;
+ private:
+ QMap<QString, QString> m_driverMap;
+};
+
+/**
+ * The MyMoneyDbColumn class is a base type for generic db columns.
+ * Derived types exist for several common column types.
+ */
+class MyMoneyDbColumn : public KShared {
+ public:
+ MyMoneyDbColumn (const QString& iname,
+ const QString& itype = QString::null,
+ const bool iprimary = false,
+ const bool inotnull = false,
+ const QString &initVersion = "0.1"):
+ m_name(iname),
+ m_type(itype),
+ m_isPrimary(iprimary),
+ m_isNotNull(inotnull),
+ m_initVersion(initVersion) {}
+ MyMoneyDbColumn (void) {}
+ virtual ~MyMoneyDbColumn () {}
+
+ /**
+ * This method is used to copy column objects. Because there are several derived types,
+ * clone() is more appropriate than a copy ctor in most cases.
+ */
+ virtual MyMoneyDbColumn* clone () const;
+
+ /**
+ * This method generates the DDL (Database Design Language) string for the column.
+ *
+ * @param dbType Database driver type
+ *
+ * @return QString of the DDL for the column, tailored for what the driver supports.
+ */
+ virtual const QString generateDDL (databaseTypeE dbType) const;
+
+ const QString& name(void) const {return (m_name);}
+ const QString& type(void) const {return (m_type);}
+ bool isPrimaryKey(void) const {return (m_isPrimary);}
+ bool isNotNull(void) const {return (m_isNotNull);}
+ private:
+ QString m_name;
+ QString m_type;
+ bool m_isPrimary;
+ bool m_isNotNull;
+ QString m_initVersion;
+};
+
+/**
+ * The MyMoneyDbDatetimeColumn class is a representation of datetime columns.
+ */
+class MyMoneyDbDatetimeColumn : public MyMoneyDbColumn {
+ public:
+ MyMoneyDbDatetimeColumn (const QString& iname,
+ const bool iprimary = false,
+ const bool inotnull = false,
+ const QString &initVersion = "0.1"):
+ MyMoneyDbColumn (iname, "", iprimary, inotnull, initVersion)
+ {}
+ virtual ~MyMoneyDbDatetimeColumn() {}
+ virtual const QString generateDDL (databaseTypeE dbType) const;
+ virtual MyMoneyDbDatetimeColumn* clone () const;
+ private:
+ static const QString calcType(void);
+};
+
+/**
+ * The MyMoneyDbColumn class is a representation of integer db columns.
+ */
+class MyMoneyDbIntColumn : public MyMoneyDbColumn {
+ public:
+ enum size {TINY, SMALL, MEDIUM, BIG};
+ MyMoneyDbIntColumn (const QString& iname,
+ const size type = MEDIUM,
+ const bool isigned = true,
+ const bool iprimary = false,
+ const bool inotnull = false,
+ const QString &initVersion = "0.1"):
+ MyMoneyDbColumn (iname, "", iprimary, inotnull, initVersion),
+ m_type (type),
+ m_isSigned (isigned) {}
+ virtual ~MyMoneyDbIntColumn() {}
+ virtual const QString generateDDL (databaseTypeE dbType) const;
+ virtual MyMoneyDbIntColumn* clone () const;
+ private:
+ size m_type;
+ bool m_isSigned;
+};
+
+/**
+ * The MyMoneyDbTextColumn class is a representation of text db columns,
+ * for drivers that support it. If the driver does not support it, it is
+ * usually some sort of really large varchar or varchar2.
+ */
+class MyMoneyDbTextColumn : public MyMoneyDbColumn {
+ public:
+ enum size {TINY, NORMAL, MEDIUM, LONG};
+ MyMoneyDbTextColumn (const QString& iname,
+ const size type = MEDIUM,
+ const bool iprimary = false,
+ const bool inotnull = false,
+ const QString &initVersion = "0.1"):
+ MyMoneyDbColumn (iname, "", iprimary, inotnull, initVersion),
+ m_type (type) {}
+ virtual ~MyMoneyDbTextColumn() {}
+ virtual const QString generateDDL (databaseTypeE dbType) const;
+ virtual MyMoneyDbTextColumn* clone () const;
+ private:
+ size m_type;
+};
+
+/**
+ * The MyMoneyDbIndex class is a representation of a db index.
+ * To provide generic support for most databases, the table name,
+ * name of the index, and list of columns for the index are required.
+ * Additionally, the user can specify whether the index is unique or not.
+ *
+ * At this time, different types of index are not supported, since the portability
+ * is fairly limited.
+ */
+class MyMoneyDbIndex {
+ public:
+ MyMoneyDbIndex (const QString& table,
+ const QString& name,
+ const QStringList& columns,
+ bool unique = false):
+ m_table(table),
+ m_unique(unique),
+ m_name(name),
+ m_columns(columns)
+ {}
+ MyMoneyDbIndex () {}
+ inline const QString table () const {return m_table;}
+ inline bool isUnique () const {return m_unique;}
+ inline const QString name () const {return m_name;}
+ inline const QStringList columns () const {return m_columns;}
+ const QString generateDDL (databaseTypeE dbType) const;
+ private:
+ QString m_table;
+ bool m_unique;
+ QString m_name;
+ QStringList m_columns;
+};
+
+/**
+ * The MyMoneyDbTable class is a representation of a db table.
+ * It has a list of the columns (pointers to MyMoneyDbColumn types) and a
+ * list of any indices that may be on the table.
+ * Additionally, a string for a parameterized query for each of some common
+ * tasks on a table is created by the ctor.
+ *
+ * Const iterators over the list of columns are provided as a convenience.
+ */
+class MyMoneyDbTable {
+ public:
+ MyMoneyDbTable (const QString& iname,
+ const QValueList<KSharedPtr <MyMoneyDbColumn> >& ifields,
+ const QString& initVersion = "1.0"):
+ m_name(iname),
+ m_fields(ifields),
+ m_initVersion(initVersion) {}
+ MyMoneyDbTable (void) {}
+
+ inline const QString& name(void) const {return (m_name);}
+ inline const QString& insertString(void) const {return (m_insertString);};
+ inline const QString selectAllString(bool terminate = true) const
+ {return (terminate ? QString(m_selectAllString + ";") : m_selectAllString);};
+ inline const QString& updateString(void) const {return (m_updateString);};
+ inline const QString& deleteString(void) const {return (m_deleteString);};
+
+ /**
+ * This method determines the string required to drop the primary key for the table
+ * based on the db specific syntax.
+ *
+ * @param dbType The driver type of the database.
+ *
+ * @return QString for the syntax to drop the primary key.
+ */
+ const QString dropPrimaryKeyString(databaseTypeE dbType) const;
+ /**
+ * This method returns a comma-separated list of all column names in the table
+ *
+ * @return QString column list.
+ */
+ const QString columnList() const;
+ /**
+ * This method returns the string for changing a column's definition. It covers statements
+ * like ALTER TABLE..CHANGE COLUMN, MODIFY COLUMN, etc.
+ *
+ * @param dbType The driver type of the database.
+ * @param columnName The name of the column to be modified.
+ * @param newDef The MyMoneyColumn object of the new column definition.
+ *
+ * @return QString containing DDL to change the column.
+ */
+ const QString modifyColumnString(databaseTypeE dbType, const QString& columnName, const MyMoneyDbColumn& newDef) const;
+
+ /**
+ * This method builds all of the SQL strings for common operations.
+ */
+ void buildSQLStrings(void);
+
+ /**
+ * This method generates the DDL required to create the table.
+ *
+ * @param dbType The driver type of the database.
+ *
+ * @return QString of the DDL.
+ */
+ const QString generateCreateSQL (databaseTypeE dbType) const;
+
+ /**
+ * This method creates a MyMoneyDbIndex object and adds it to the list of indices for the table.
+ *
+ * @param name The name of the index.
+ * @param columns The list of the columns affected.
+ * @param unique Whether or not this should be a unique index.
+ */
+ void addIndex(const QString& name, const QStringList& columns, bool unique = false);
+
+ typedef QValueList<KSharedPtr <MyMoneyDbColumn> >::const_iterator field_iterator;
+ inline field_iterator begin(void) const {return m_fields.constBegin();}
+ inline field_iterator end(void) const {return m_fields.constEnd(); }
+ private:
+ QString m_name;
+ QValueList<KSharedPtr <MyMoneyDbColumn> > m_fields;
+
+ typedef QValueList<MyMoneyDbIndex>::const_iterator index_iterator;
+ QValueList<MyMoneyDbIndex> m_indices;
+ QString m_initVersion;
+ QString m_insertString; // string to insert a record
+ QString m_selectAllString; // to select all fields
+ QString m_updateString; // normal string for record update
+ QString m_deleteString; // string to delete 1 record
+};
+
+/**
+ * The MyMoneyDbView class is a representation of a db view.
+ *
+ * Views will be dropped and recreated on upgrade, so there is no need
+ * to do anything more complex than storing the name of the view and
+ * the CREATE VIEW string.
+ */
+class MyMoneyDbView {
+ public:
+ MyMoneyDbView (const QString& name,
+ const QString& createString,
+ const QString& initVersion = "0.1")
+ : m_name (name), m_createString (createString), m_initVersion (initVersion)
+ {}
+
+ MyMoneyDbView (void) {}
+
+ inline const QString& name(void) const {return (m_name);}
+ inline const QString createString(void) const {return (m_createString);};
+
+ private:
+ QString m_name;
+ QString m_createString;
+ QString m_initVersion;
+};
+
+/**
+ * The MyMoneyDbDef class is
+ */
+class MyMoneyDbDef {
+ friend class MyMoneyStorageSql;
+ friend class MyMoneyDatabaseMgr;
+public:
+ MyMoneyDbDef();
+ ~MyMoneyDbDef() {}
+
+ const QString generateSQL (const QString& driver) const;
+
+ typedef QMap<QString, MyMoneyDbTable>::const_iterator table_iterator;
+ inline table_iterator tableBegin(void) const {return m_tables.constBegin();}
+ inline table_iterator tableEnd(void) const {return m_tables.constEnd();}
+
+ typedef QMap<QString, MyMoneyDbView>::const_iterator view_iterator;
+ inline view_iterator viewBegin(void) const {return m_views.constBegin();}
+ inline view_iterator viewEnd(void) const {return m_views.constEnd();}
+
+ inline unsigned int currentVersion() const {return (m_currentVersion);};
+
+private:
+ const QString enclose(const QString& text) const
+ {return (QString("'" + text + "'"));};
+ static unsigned int m_currentVersion; // The current version of the database layout
+ MyMoneyDbDrivers m_drivers;
+#define TABLE(name) void name();
+#define VIEW(name) void name();
+ TABLE(FileInfo);
+ TABLE(Institutions);
+ TABLE(Payees);
+ TABLE(Accounts);
+ TABLE(Transactions);
+ TABLE(Splits);
+ TABLE(KeyValuePairs);
+ TABLE(Schedules);
+ TABLE(SchedulePaymentHistory);
+ TABLE(Securities);
+ TABLE(Prices);
+ TABLE(Currencies);
+ TABLE(Reports);
+ TABLE(Budgets);
+ VIEW(Balances);
+protected:
+ QMap<QString, MyMoneyDbTable> m_tables;
+ QMap<QString, MyMoneyDbView> m_views;
+};
+
+class IMyMoneySerialize;
+
+/**
+ * The MyMoneyDbColumn class is a base type for generic db columns.
+ * Derived types exist for several common column types.
+ */
+class MyMoneyStorageSql : public IMyMoneyStorageFormat, public QSqlDatabase, public KShared {
+public:
+
+ MyMoneyStorageSql (IMyMoneySerialize *storage, const KURL& = KURL());
+ virtual ~MyMoneyStorageSql() {close(true);}
+
+ unsigned int currentVersion() const {return (m_db.currentVersion());};
+
+ /**
+ * MyMoneyStorageSql - open database file
+ *
+ * @param url pseudo-URL of database to be opened
+ * @param openMode open mode, same as for QFile::open
+ * @param clear whether existing data can be deleted
+
+ * @return 0 - database successfully opened
+ * @return 1 - database not opened, use lastError function for reason
+ * @return -1 - output database not opened, contains data, clean not specified
+ *
+ */
+ int open(const KURL& url, int openMode, bool clear = false);
+ /**
+ * MyMoneyStorageSql close the database
+ *
+ * @return void
+ *
+ */
+ void close(bool logoff = true);
+ /**
+ * MyMoneyStorageSql read all the database into storage
+ *
+ * @return void
+ *
+ */
+ bool readFile(void);
+ /**
+ * MyMoneyStorageSql write/update the database from storage
+ *
+ * @return void
+ *
+ */
+ bool writeFile(void);
+
+ // check database type
+ bool isDb2() const { return (m_dbType == Db2);};
+ bool isInterbase() const { return (m_dbType == Interbase);};
+ bool isMysql() const { return (m_dbType == Mysql);};
+ bool isOracle8() const { return (m_dbType == Oracle8);};
+ bool isODBC3() const { return (m_dbType == ODBC3);};
+ bool isPostgresql() const { return (m_dbType == Postgresql);};
+ bool isSybase() const { return (m_dbType == Sybase);};
+ bool isSqlite3() const { return (m_dbType == Sqlite3);};
+
+ /**
+ * MyMoneyStorageSql generalized error routine
+ *
+ * @return : error message to be displayed
+ *
+ */
+ const QString& lastError() const {return (m_error);};
+ /**
+ * This method is used when a database file is open, and the data is to
+ * be saved in a different file or format. It will ensure that all data
+ * from the database is available in memory to enable it to be written.
+ */
+ virtual void fillStorage();
+ /**
+ * The following functions correspond to the identically named (usually) functions
+ * within the Storage Manager, and are called to update the database
+ */
+ void modifyUserInfo(const MyMoneyPayee& payee);
+ void addInstitution(const MyMoneyInstitution& inst);
+ void modifyInstitution(const MyMoneyInstitution& inst);
+ void removeInstitution(const MyMoneyInstitution& inst);
+ void addPayee(const MyMoneyPayee& payee);
+ void modifyPayee(const MyMoneyPayee& payee);
+ void removePayee(const MyMoneyPayee& payee);
+ void addAccount(const MyMoneyAccount& acc);
+ void modifyAccount(const MyMoneyAccount& acc);
+ void removeAccount(const MyMoneyAccount& acc);
+ void addTransaction(const MyMoneyTransaction& tx);
+ void modifyTransaction(const MyMoneyTransaction& tx);
+ void removeTransaction(const MyMoneyTransaction& tx);
+ void addSchedule(const MyMoneySchedule& sch);
+ void modifySchedule(const MyMoneySchedule& sch);
+ void removeSchedule(const MyMoneySchedule& sch);
+ void addSecurity(const MyMoneySecurity& sec);
+ void modifySecurity(const MyMoneySecurity& sec);
+ void removeSecurity(const MyMoneySecurity& sec);
+ void addPrice(const MyMoneyPrice& p);
+ void removePrice(const MyMoneyPrice& p);
+ void addCurrency(const MyMoneySecurity& sec);
+ void modifyCurrency(const MyMoneySecurity& sec);
+ void removeCurrency(const MyMoneySecurity& sec);
+ void addReport(const MyMoneyReport& rep);
+ void modifyReport(const MyMoneyReport& rep);
+ void removeReport(const MyMoneyReport& rep);
+ void addBudget(const MyMoneyBudget& bud);
+ void modifyBudget(const MyMoneyBudget& bud);
+ void removeBudget(const MyMoneyBudget& bud);
+
+ unsigned long transactionCount (const QString& aid = QString()) const;
+ inline const QMap<QString, unsigned long> transactionCountMap () const
+ {return (m_transactionCountMap);};
+ /**
+ * the storage manager also needs the following read entry points
+ */
+ const QMap<QString, MyMoneyAccount> fetchAccounts (const QStringList& idList = QStringList (), bool forUpdate = false) const;
+ const QMap<QString, MyMoneyMoney> fetchBalance(const QStringList& id, const QDate& date) const;
+ const QMap<QString, MyMoneyBudget> fetchBudgets (const QStringList& idList = QStringList (), bool forUpdate = false) const;
+ const QMap<QString, MyMoneySecurity> fetchCurrencies (const QStringList& idList = QStringList (), bool forUpdate = false) const;
+ const QMap<QString, MyMoneyInstitution> fetchInstitutions (const QStringList& idList = QStringList (), bool forUpdate = false) const;
+ const QMap<QString, MyMoneyPayee> fetchPayees (const QStringList& idList = QStringList (), bool forUpdate = false) const;
+ const MyMoneyPriceList fetchPrices (const QStringList& fromIdList = QStringList (), const QStringList& toIdList = QStringList(), bool forUpdate = false) const;
+ const MyMoneyPrice fetchSinglePrice (const QString& fromIdList, const QString& toIdList, const QDate& date, bool exactDate, bool forUpdate = false) const;
+ const QMap<QString, MyMoneyReport> fetchReports (const QStringList& idList = QStringList (), bool forUpdate = false) const;
+ const QMap<QString, MyMoneySchedule> fetchSchedules (const QStringList& idList = QStringList (), bool forUpdate = false) const;
+ const QMap<QString, MyMoneySecurity> fetchSecurities (const QStringList& idList = QStringList (), bool forUpdate = false) const;
+ const QMap<QString, MyMoneyTransaction> fetchTransactions (const QString& tidList = QString (), const QString& dateClause = QString(), bool forUpdate = false) const;
+ const QMap<QString, MyMoneyTransaction> fetchTransactions (const MyMoneyTransactionFilter& filter) const;
+ bool isReferencedByTransaction(const QString& id) const;
+
+ void readPayees(const QString&);
+ void readPayees(const QValueList<QString> payeeList = QValueList<QString>());
+ void readTransactions(const MyMoneyTransactionFilter& filter);
+ void setProgressCallback(void(*callback)(int, int, const QString&));
+
+ virtual void readFile(QIODevice* s, IMyMoneySerialize* storage) { Q_UNUSED(s); Q_UNUSED(storage) };
+ virtual void writeFile(QIODevice* s, IMyMoneySerialize* storage){ Q_UNUSED(s); Q_UNUSED(storage) };
+
+ void startCommitUnit (const QString& callingFunction);
+ bool endCommitUnit (const QString& callingFunction);
+ void cancelCommitUnit (const QString& callingFunction);
+
+ long unsigned getRecCount(const QString& table) const;
+ long unsigned getNextBudgetId() const;
+ long unsigned getNextAccountId() const;
+ long unsigned getNextInstitutionId() const;
+ long unsigned getNextPayeeId() const;
+ long unsigned getNextReportId() const;
+ long unsigned getNextScheduleId() const;
+ long unsigned getNextSecurityId() const;
+ long unsigned getNextTransactionId() const;
+
+ long unsigned incrementBudgetId();
+ long unsigned incrementAccountId();
+ long unsigned incrementInstitutionId();
+ long unsigned incrementPayeeId();
+ long unsigned incrementReportId();
+ long unsigned incrementScheduleId();
+ long unsigned incrementSecurityId();
+ long unsigned incrementTransactionId();
+
+ void loadAccountId(const unsigned long& id);
+ void loadTransactionId(const unsigned long& id);
+ void loadPayeeId(const unsigned long& id);
+ void loadInstitutionId(const unsigned long& id);
+ void loadScheduleId(const unsigned long& id);
+ void loadSecurityId(const unsigned long& id);
+ void loadReportId(const unsigned long& id);
+ void loadBudgetId(const unsigned long& id);
+
+private:
+ // a function to build a comprehensive error message
+ QString& buildError (const QSqlQuery& q, const QString& function, const QString& message) const;
+ // write routines
+ void writeUserInformation(void);
+ void writeInstitutions(void);
+ void writePayees(void);
+ void writeAccounts(void);
+ void writeTransactions(void);
+ void writeSchedules(void);
+ void writeSecurities(void);
+ void writePrices(void);
+ void writeCurrencies(void);
+ void writeFileInfo(void);
+ void writeReports(void);
+ void writeBudgets(void);
+
+ void writeInstitution(const MyMoneyInstitution& i, MyMoneySqlQuery& q);
+ void writePayee(const MyMoneyPayee& p, MyMoneySqlQuery& q, bool isUserInfo = false);
+ void writeAccount (const MyMoneyAccount& a, MyMoneySqlQuery& q);
+ void writeTransaction(const QString& txId, const MyMoneyTransaction& tx, MyMoneySqlQuery& q, const QString& type);
+ void writeSplits(const QString& txId, const QString& type, const QValueList<MyMoneySplit>& splitList);
+ void writeSplit(const QString& txId, const MyMoneySplit& split, const QString& type, const int splitId, MyMoneySqlQuery& q);
+ void writeSchedule(const MyMoneySchedule& sch, MyMoneySqlQuery& q, bool insert);
+ void writeSecurity(const MyMoneySecurity& security, MyMoneySqlQuery& q);
+ void writePricePair ( const MyMoneyPriceEntries& p);
+ void writePrice (const MyMoneyPrice& p);
+ void writeCurrency(const MyMoneySecurity& currency, MyMoneySqlQuery& q);
+ void writeReport (const MyMoneyReport& rep, MyMoneySqlQuery& q);
+ void writeBudget (const MyMoneyBudget& bud, MyMoneySqlQuery& q);
+ void writeKeyValuePairs(const QString& kvpType, const QString& kvpId, const QMap<QString, QString>& pairs);
+ void writeKeyValuePair(const QString& kvpType, const QString& kvpId,
+ const QString& kvpKey, const QString& kvpData);
+ // read routines
+ void readFileInfo(void);
+ void readLogonData(void);
+ void readUserInformation(void);
+ void readInstitutions(void);
+ void readAccounts(void);
+ void readTransaction(const QString id);
+ void readTransactions(const QString& tidList = QString(), const QString& dateClause = QString());
+ void readTransaction(MyMoneyTransaction &tx, const QString& tid);
+ void readSplit (MyMoneySplit& s, const MyMoneySqlQuery& q, const MyMoneyDbTable& t) const;
+ const MyMoneyKeyValueContainer readKeyValuePairs (const QString& kvpType, const QString& kvpId) const;
+ const QMap<QString, MyMoneyKeyValueContainer> readKeyValuePairs (const QString& kvpType, const QStringList& kvpIdList) const;
+ void readSchedules(void);
+ void readSecurities(void);
+ void readPrices(void);
+ void readCurrencies(void);
+ void readReports(void);
+ void readBudgets(void);
+
+ void deleteTransaction(const QString& id);
+ void deleteSchedule(const QString& id);
+ void deleteKeyValuePairs(const QString& kvpType, const QString& kvpId);
+ long unsigned calcHighId (const long unsigned&, const QString&);
+
+ void setVersion (const QString& version);
+
+ void signalProgress(int current, int total, const QString& = "") const;
+ void (*m_progressCallback)(int, int, const QString&);
+
+ //void startCommitUnit (const QString& callingFunction);
+ //void endCommitUnit (const QString& callingFunction);
+ //void cancelCommitUnit (const QString& callingFunction);
+ int splitState(const MyMoneyTransactionFilter::stateOptionE& state) const;
+
+ inline const QDate getDate (const QString& date) const {
+ return (date.isNull() ? QDate() : QDate::fromString(date, Qt::ISODate));
+ }
+
+ inline const QDateTime getDateTime (const QString& date) const {
+ return (date.isNull() ? QDateTime() : QDateTime::fromString(date, Qt::ISODate));
+ }
+
+ // open routines
+ /**
+ * MyMoneyStorageSql create database
+ *
+ * @param url pseudo-URL of database to be opened
+ *
+ * @return true - creation successful
+ * @return false - could not create
+ *
+ */
+ int createDatabase(const KURL& url);
+ int upgradeDb();
+ int upgradeToV1();
+ int upgradeToV2();
+ int upgradeToV3();
+ int upgradeToV4();
+ int upgradeToV5();
+ int upgradeToV6();
+ bool sqliteAlterTable(const MyMoneyDbTable& t);
+ bool addColumn(const MyMoneyDbTable& t,
+ const MyMoneyDbColumn& c,
+ const QString& after = QString());
+ bool addColumn(const QString& table,
+ const QString& column,
+ const QString& after = QString());
+ bool dropColumn(const MyMoneyDbTable& t,
+ const QString& c);
+ bool dropColumn(const QString& table,
+ const QString& column);
+
+// long long unsigned getRecCount(const QString& table);
+ int createTables();
+ void createTable(const MyMoneyDbTable& t);
+ void clean ();
+ int isEmpty();
+ // data
+ MyMoneyDbDrivers m_drivers;
+ databaseTypeE m_dbType;
+
+ MyMoneyDbDef m_db;
+ unsigned int m_dbVersion;
+ IMyMoneySerialize *m_storage;
+ IMyMoneyStorage *m_storagePtr;
+ // input options
+ bool m_loadAll; // preload all data
+ bool m_override; // override open if already in use
+ // error message
+ QString m_error;
+ // record counts
+ long unsigned m_institutions;
+ long unsigned m_accounts;
+ long unsigned m_payees;
+ long unsigned m_transactions;
+ long unsigned m_splits;
+ long unsigned m_securities;
+ long unsigned m_prices;
+ long unsigned m_currencies;
+ long unsigned m_schedules;
+ long unsigned m_reports;
+ long unsigned m_kvps;
+ long unsigned m_budgets;
+ // next id to use (for future archive)
+ long unsigned m_hiIdInstitutions;
+ long unsigned m_hiIdPayees;
+ long unsigned m_hiIdAccounts;
+ long unsigned m_hiIdTransactions;
+ long unsigned m_hiIdSchedules;
+ long unsigned m_hiIdSecurities;
+ long unsigned m_hiIdReports;
+ long unsigned m_hiIdBudgets;
+ // encrypt option - usage TBD
+ QString m_encryptData;
+
+ /**
+ * This variable is used to suppress status messages except during
+ * initial data load and final write
+
+ */
+ bool m_displayStatus;
+ /**
+ * On occasions, e.g. after a complex transaction search, or for populating a
+ * payee popup list, it becomes necessary to load all data into memory. The
+ * following flags will be set after such a load, to indicate that further
+ * retrievals are not needed.
+ */
+// bool m_transactionListRead;
+// bool m_payeeListRead;
+ /**
+ * This member variable holds a list of those accounts for which all
+ * transactions are in memory, thus saving reading them again
+ */
+// QValueList<QString> m_accountsLoaded;
+ /**
+ * This member variable is used when loading transactions to list all
+ * referenced payees, which can then be read into memory (if not already there)
+ */
+// QValueList<QString> m_payeeList;
+
+ void alert(QString s) const {qDebug("%s", s.ascii());}; // FIXME: remove...
+ /** The following keeps track of commitment units (known as transactions in SQL
+ * though it would be confusing to use that term within KMM). It is implemented
+ * as a stack for debug purposes. Long term, probably a count would suffice
+ */
+ QValueStack<QString> m_commitUnitStack;
+ /**
+ * This member variable is used to preload transactions for preferred accounts
+ */
+ MyMoneyTransactionFilter m_preferred;
+ /**
+ * This member variable is used because reading prices from a file uses the 'add...' function rather than a
+ * 'load...' function which other objects use. Having this variable allows us to avoid needing to check the
+ * database to see if this really is a new or modified price
+ */
+ bool m_readingPrices;
+ /**
+ * This member variable holds a map of transaction counts per account, indexed by
+ * the account id. It is used
+ * to avoid having to scan all transactions whenever a count is needed. It should
+ * probably be moved into the MyMoneyAccount object; maybe we will do that once
+ * the database code has been properly checked out
+ */
+ QMap<QString, unsigned long> m_transactionCountMap;
+ /**
+ * These member variables hold the user name and date/time of logon
+ */
+ QString m_logonUser;
+ QDateTime m_logonAt;
+ QDateTime m_txPostDate; // FIXME: remove when Tom puts date into split object
+
+ //Disable copying
+ MyMoneyStorageSql (const MyMoneyStorageSql& rhs);
+ MyMoneyStorageSql& operator= (const MyMoneyStorageSql& rhs);
+ //
+ bool m_newDatabase;
+};
+#endif // MYMONEYSTORAGESQL_H
diff --git a/kmymoney2/mymoney/storage/mymoneystoragexml.cpp b/kmymoney2/mymoney/storage/mymoneystoragexml.cpp
new file mode 100644
index 0000000..e8027d1
--- /dev/null
+++ b/kmymoney2/mymoney/storage/mymoneystoragexml.cpp
@@ -0,0 +1,908 @@
+/***************************************************************************
+ mymoneystoragexml.cpp - description
+ -------------------
+ begin : Thu Oct 24 2002
+ copyright : (C) 2002 by Kevin Tambascio
+ (C) 2004 by Thomas Baumgart
+ email : Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "config.h"
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qfile.h>
+#include <qdom.h>
+#include <qmap.h>
+#include <qxml.h>
+
+// ----------------------------------------------------------------------------
+// KDE Includes
+
+#include "kdecompat.h"
+#include <klocale.h>
+#include <kdebug.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "mymoneystoragexml.h"
+#include "../mymoneyreport.h"
+#include "../mymoneybudget.h"
+#include "../mymoneyinstitution.h"
+
+unsigned int MyMoneyStorageXML::fileVersionRead = 0;
+unsigned int MyMoneyStorageXML::fileVersionWrite = 0;
+
+
+class MyMoneyStorageXML::Private
+{
+ friend class MyMoneyStorageXML;
+public:
+ Private() {}
+
+ QMap<QString, MyMoneyInstitution> iList;
+ QMap<QString, MyMoneyAccount> aList;
+ QMap<QString, MyMoneyTransaction> tList;
+ QMap<QString, MyMoneyPayee> pList;
+ QMap<QString, MyMoneySchedule> sList;
+ QMap<QString, MyMoneySecurity> secList;
+ QMap<QString, MyMoneyReport> rList;
+ QMap<QString, MyMoneyBudget> bList;
+ QMap<MyMoneySecurityPair, MyMoneyPriceEntries> prList;
+
+ QString m_fromSecurity;
+ QString m_toSecurity;
+
+};
+
+
+class MyMoneyXmlContentHandler : public QXmlContentHandler
+{
+public:
+ MyMoneyXmlContentHandler(MyMoneyStorageXML* reader);
+ virtual ~MyMoneyXmlContentHandler() {}
+ virtual void setDocumentLocator (QXmlLocator * locator) { m_loc = locator; }
+ virtual bool startDocument (void);
+ virtual bool endDocument (void);
+ virtual bool startPrefixMapping(const QString & prefix, const QString & uri);
+ virtual bool endPrefixMapping(const QString & prefix);
+ virtual bool startElement(const QString & namespaceURI, const QString & localName, const QString & qName, const QXmlAttributes & atts);
+ virtual bool endElement(const QString & namespaceURI, const QString & localName, const QString & qName);
+ virtual bool characters(const QString & ch);
+ virtual bool ignorableWhitespace(const QString & ch);
+ virtual bool processingInstruction(const QString & target, const QString & data);
+ virtual bool skippedEntity(const QString & name);
+ virtual QString errorString(void);
+
+private:
+ MyMoneyStorageXML* m_reader;
+ QXmlLocator* m_loc;
+ int m_level;
+ int m_elementCount;
+ QDomDocument m_doc;
+ QDomElement m_baseNode;
+ QDomElement m_currNode;
+ QString m_errMsg;
+};
+
+MyMoneyXmlContentHandler::MyMoneyXmlContentHandler(MyMoneyStorageXML* reader) :
+ m_reader(reader),
+ m_loc(0),
+ m_level(0),
+ m_elementCount(0)
+{
+}
+
+bool MyMoneyXmlContentHandler::startDocument(void)
+{
+ qDebug("startDocument");
+ return true;
+}
+
+bool MyMoneyXmlContentHandler::endDocument(void)
+{
+ qDebug("endDocument");
+ return true;
+}
+
+bool MyMoneyXmlContentHandler::skippedEntity (const QString & /* name */)
+{
+ // qDebug(QString("Skipped entity '%1'").arg(name));
+ return true;
+}
+
+bool MyMoneyXmlContentHandler::startPrefixMapping (const QString& /*prefix */, const QString & /* uri */)
+{
+ // qDebug(QString("start prefix '%1', '%2'").arg(prefix).arg(uri));
+ return true;
+}
+
+bool MyMoneyXmlContentHandler::endPrefixMapping (const QString& /* prefix */)
+{
+ // qDebug(QString("end prefix '%1'").arg(prefix));
+ return true;
+}
+
+bool MyMoneyXmlContentHandler::startElement (const QString& /* namespaceURI */, const QString& /* localName */, const QString& qName, const QXmlAttributes & atts)
+{
+ if(m_level == 0) {
+ QString s = qName.lower();
+ if(s == "transaction"
+ || s == "account"
+ || s == "price"
+ || s == "payee"
+ || s == "currency"
+ || s == "security"
+ || s == "keyvaluepairs"
+ || s == "institution"
+ || s == "report"
+ || s == "budget"
+ || s == "fileinfo"
+ || s == "user"
+ || s == "scheduled_tx") {
+ m_baseNode = m_doc.createElement(qName);
+ for(int i=0; i < atts.count(); ++i) {
+ m_baseNode.setAttribute(atts.qName(i), atts.value(i));
+ }
+ m_currNode = m_baseNode;
+ m_level = 1;
+
+ } else if(s == "transactions") {
+ qDebug("reading transactions");
+ if(atts.count()) {
+ int count = atts.value(QString("count")).toUInt();
+ m_reader->signalProgress(0, count, i18n("Loading transactions..."));
+ m_elementCount = 0;
+ }
+ } else if(s == "accounts") {
+ qDebug("reading accounts");
+ if(atts.count()) {
+ int count = atts.value(QString("count")).toUInt();
+ m_reader->signalProgress(0, count, i18n("Loading accounts..."));
+ m_elementCount = 0;
+ }
+ } else if(s == "securities") {
+ qDebug("reading securities");
+ if(atts.count()) {
+ int count = atts.value(QString("count")).toUInt();
+ m_reader->signalProgress(0, count, i18n("Loading securities..."));
+ m_elementCount = 0;
+ }
+ } else if(s == "reports") {
+ qDebug("reading reports");
+ if(atts.count()) {
+ int count = atts.value(QString("count")).toUInt();
+ m_reader->signalProgress(0, count, i18n("Loading reports..."));
+ m_elementCount = 0;
+ }
+ } else if(s == "prices") {
+ qDebug("reading prices");
+ m_elementCount = 0;
+ } else if(s == "pricepair") {
+ if(atts.count()) {
+ m_reader->d->m_fromSecurity = atts.value(QString("from"));
+ m_reader->d->m_toSecurity = atts.value(QString("to"));
+ }
+ }
+
+ } else {
+ m_level++;
+ QDomElement node = m_doc.createElement(qName);
+ for(int i=0; i < atts.count(); ++i) {
+ node.setAttribute(atts.qName(i), atts.value(i));
+ }
+ m_currNode.appendChild(node);
+ m_currNode = node;
+ }
+ return true;
+}
+
+bool MyMoneyXmlContentHandler::endElement(const QString& /* namespaceURI */, const QString& /* localName */, const QString& qName)
+{
+ bool rc = true;
+ QString s = qName.lower();
+ if(m_level) {
+ m_currNode = m_currNode.parentNode().toElement();
+ m_level--;
+ if(!m_level) {
+ try {
+ if(s == "transaction") {
+ MyMoneyTransaction t(m_baseNode);
+ if(!t.id().isEmpty())
+ m_reader->d->tList[t.uniqueSortKey()] = t;
+ } else if(s == "account") {
+ MyMoneyAccount a(m_baseNode);
+ if(!a.id().isEmpty())
+ m_reader->d->aList[a.id()] = a;
+ } else if(s == "payee") {
+ MyMoneyPayee p(m_baseNode);
+ if(!p.id().isEmpty())
+ m_reader->d->pList[p.id()] = p;
+ } else if(s == "currency") {
+ MyMoneySecurity s(m_baseNode);
+ if(!s.id().isEmpty())
+ m_reader->d->secList[s.id()] = s;
+ } else if(s == "security") {
+ MyMoneySecurity s(m_baseNode);
+ if(!s.id().isEmpty())
+ m_reader->d->secList[s.id()] = s;
+ } else if(s == "keyvaluepairs") {
+ MyMoneyKeyValueContainer kvp(m_baseNode);
+ m_reader->m_storage->setPairs(kvp.pairs());
+ } else if(s == "institution") {
+ MyMoneyInstitution i(m_baseNode);
+ if(!i.id().isEmpty())
+ m_reader->d->iList[i.id()] = i;
+ } else if(s == "report") {
+ MyMoneyReport r(m_baseNode);
+ if(!r.id().isEmpty())
+ m_reader->d->rList[r.id()] = r;
+ } else if(s == "budget") {
+ MyMoneyBudget b(m_baseNode);
+ if(!b.id().isEmpty())
+ m_reader->d->bList[b.id()] = b;
+ } else if(s == "fileinfo") {
+ rc = m_reader->readFileInformation(m_baseNode);
+ } else if(s == "user") {
+ rc = m_reader->readUserInformation(m_baseNode);
+ } else if(s == "scheduled_tx") {
+ MyMoneySchedule s(m_baseNode);
+ if(!s.id().isEmpty())
+ m_reader->d->sList[s.id()] = s;
+ } else if(s == "price") {
+ MyMoneyPrice p(m_reader->d->m_fromSecurity, m_reader->d->m_toSecurity, m_baseNode);
+ m_reader->d->prList[MyMoneySecurityPair(m_reader->d->m_fromSecurity, m_reader->d->m_toSecurity)][p.date()] = p;
+ } else {
+ m_errMsg = i18n("Unknown XML tag %1 found in line %2").arg(qName).arg(m_loc->lineNumber());
+ kdWarning() << m_errMsg << endl;
+ rc = false;
+ }
+ m_reader->signalProgress(++m_elementCount, 0);
+ } catch(MyMoneyException* e) {
+ m_errMsg = i18n("Exception while creating a %1 element: %2").arg(s).arg(e->what());
+ kdWarning() << m_errMsg << endl;
+ delete e;
+ rc = false;
+ }
+ m_doc = QDomDocument();
+ }
+ } else {
+ if(s == "institutions") {
+ // last institution read, now dump them into the engine
+ m_reader->m_storage->loadInstitutions(m_reader->d->iList);
+ m_reader->d->iList.clear();
+ m_reader->signalProgress(-1, -1);
+ } else if(s == "accounts") {
+ // last account read, now dump them into the engine
+ m_reader->m_storage->loadAccounts(m_reader->d->aList);
+ m_reader->d->aList.clear();
+ m_reader->signalProgress(-1, -1);
+ } else if(s == "payees") {
+ // last payee read, now dump them into the engine
+ m_reader->m_storage->loadPayees(m_reader->d->pList);
+ m_reader->d->pList.clear();
+ m_reader->signalProgress(-1, -1);
+ } else if(s == "transactions") {
+ // last transaction read, now dump them into the engine
+ m_reader->m_storage->loadTransactions(m_reader->d->tList);
+ m_reader->d->tList.clear();
+ m_reader->signalProgress(-1, -1);
+ } else if(s == "schedules") {
+ // last schedule read, now dump them into the engine
+ m_reader->m_storage->loadSchedules(m_reader->d->sList);
+ m_reader->d->sList.clear();
+ m_reader->signalProgress(-1, -1);
+ } else if(s == "securities") {
+ // last security read, now dump them into the engine
+ m_reader->m_storage->loadSecurities(m_reader->d->secList);
+ m_reader->d->secList.clear();
+ m_reader->signalProgress(-1, -1);
+ } else if(s == "currencies") {
+ // last currency read, now dump them into the engine
+ m_reader->m_storage->loadCurrencies(m_reader->d->secList);
+ m_reader->d->secList.clear();
+ m_reader->signalProgress(-1, -1);
+ } else if(s == "reports") {
+ // last report read, now dump them into the engine
+ m_reader->m_storage->loadReports(m_reader->d->rList);
+ m_reader->d->rList.clear();
+ m_reader->signalProgress(-1, -1);
+ } else if(s == "budgets") {
+ // last budget read, now dump them into the engine
+ m_reader->m_storage->loadBudgets(m_reader->d->bList);
+ m_reader->d->bList.clear();
+ m_reader->signalProgress(-1, -1);
+ } else if(s == "prices") {
+ // last price read, now dump them into the engine
+ m_reader->m_storage->loadPrices(m_reader->d->prList);
+ m_reader->d->bList.clear();
+ m_reader->signalProgress(-1, -1);
+ }
+ }
+ return rc;
+}
+
+bool MyMoneyXmlContentHandler::characters(const QString& /* ch */)
+{
+ return true;
+}
+
+bool MyMoneyXmlContentHandler::ignorableWhitespace(const QString& /* ch */)
+{
+ return true;
+}
+
+bool MyMoneyXmlContentHandler::processingInstruction(const QString& /* target */, const QString& /* data */)
+{
+ return true;
+}
+
+QString MyMoneyXmlContentHandler::errorString(void)
+{
+ return m_errMsg;
+}
+
+
+
+
+
+
+
+MyMoneyStorageXML::MyMoneyStorageXML() :
+ m_storage(0),
+ m_doc(0),
+ d(new Private())
+{
+}
+
+MyMoneyStorageXML::~MyMoneyStorageXML()
+{
+ delete d;
+}
+
+// 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 = NULL;
+ signalProgress(-1, -1);
+ throw new 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 = NULL;
+
+ // this seems to be nonsense, but it clears the dirty flag
+ // as a side-effect.
+ m_storage->setLastModificationDate(m_storage->lastModificationDate());
+ m_storage = NULL;
+
+ //hides the progress bar.
+ signalProgress(-1, -1);
+}
+
+void MyMoneyStorageXML::writeFile(QIODevice* qf, IMyMoneySerialize* storage)
+{
+ Q_CHECK_PTR(qf);
+ Q_CHECK_PTR(storage);
+ if(!storage)
+ {
+ return;
+ }
+ m_storage = storage;
+
+ // qDebug("XMLWRITER: Starting file write");
+ m_doc = new QDomDocument("KMYMONEY-FILE");
+ Q_CHECK_PTR(m_doc);
+ QDomProcessingInstruction instruct = m_doc->createProcessingInstruction("xml", "version=\"1.0\" encoding=\"utf-8\"");
+ m_doc->appendChild(instruct);
+
+ QDomElement mainElement = m_doc->createElement("KMYMONEY-FILE");
+ m_doc->appendChild(mainElement);
+
+ QDomElement fileInfo = m_doc->createElement("FILEINFO");
+ writeFileInformation(fileInfo);
+ mainElement.appendChild(fileInfo);
+
+ QDomElement userInfo = m_doc->createElement("USER");
+ writeUserInformation(userInfo);
+ mainElement.appendChild(userInfo);
+
+ QDomElement institutions = m_doc->createElement("INSTITUTIONS");
+ writeInstitutions(institutions);
+ mainElement.appendChild(institutions);
+
+ QDomElement payees = m_doc->createElement("PAYEES");
+ writePayees(payees);
+ mainElement.appendChild(payees);
+
+ QDomElement accounts = m_doc->createElement("ACCOUNTS");
+ writeAccounts(accounts);
+ mainElement.appendChild(accounts);
+
+ QDomElement transactions = m_doc->createElement("TRANSACTIONS");
+ writeTransactions(transactions);
+ mainElement.appendChild(transactions);
+
+ QDomElement keyvalpairs = writeKeyValuePairs(m_storage->pairs());
+ mainElement.appendChild(keyvalpairs);
+
+ QDomElement schedules = m_doc->createElement("SCHEDULES");
+ writeSchedules(schedules);
+ mainElement.appendChild(schedules);
+
+ QDomElement equities = m_doc->createElement("SECURITIES");
+ writeSecurities(equities);
+ mainElement.appendChild(equities);
+
+ QDomElement currencies = m_doc->createElement("CURRENCIES");
+ writeCurrencies(currencies);
+ mainElement.appendChild(currencies);
+
+ QDomElement prices = m_doc->createElement("PRICES");
+ writePrices(prices);
+ mainElement.appendChild(prices);
+
+ QDomElement reports = m_doc->createElement("REPORTS");
+ writeReports(reports);
+ mainElement.appendChild(reports);
+
+ QDomElement budgets = m_doc->createElement("BUDGETS");
+ writeBudgets(budgets);
+ mainElement.appendChild(budgets);
+
+ QTextStream stream(qf);
+ stream.setEncoding(QTextStream::UnicodeUTF8);
+ stream << m_doc->toString();
+
+ delete m_doc;
+ m_doc = NULL;
+
+ //hides the progress bar.
+ signalProgress(-1, -1);
+
+ // this seems to be nonsense, but it clears the dirty flag
+ // as a side-effect.
+ m_storage->setLastModificationDate(m_storage->lastModificationDate());
+
+ m_storage = NULL;
+}
+
+bool MyMoneyStorageXML::readFileInformation(const QDomElement& fileInfo)
+{
+ signalProgress(0, 3, i18n("Loading file information..."));
+ bool rc = true;
+ QDomElement temp = findChildElement("CREATION_DATE", fileInfo);
+ if (temp == QDomElement()) {
+ rc = false;
+ }
+ QString strDate = QStringEmpty(temp.attribute("date"));
+ m_storage->setCreationDate(stringToDate(strDate));
+ signalProgress(1, 0);
+
+ temp = findChildElement("LAST_MODIFIED_DATE", fileInfo);
+ if (temp == QDomElement()) {
+ rc = false;
+ }
+ strDate = QStringEmpty(temp.attribute("date"));
+ m_storage->setLastModificationDate(stringToDate(strDate));
+ signalProgress(2, 0);
+
+ temp = findChildElement("VERSION", fileInfo);
+ if (temp == QDomElement()) {
+ rc = false;
+ }
+ QString strVersion = QStringEmpty(temp.attribute("id"));
+ fileVersionRead = strVersion.toUInt(NULL, 16);
+
+ temp = findChildElement("FIXVERSION", fileInfo);
+ if (temp != QDomElement()) {
+ QString strFixVersion = QStringEmpty(temp.attribute("id"));
+ m_storage->setFileFixVersion (strFixVersion.toUInt());
+ }
+ // FIXME The old version stuff used this rather odd number
+ // We now use increments
+ if(fileVersionRead == VERSION_0_60_XML)
+ fileVersionRead = 1;
+ signalProgress(3, 0);
+
+ return rc;
+}
+
+void MyMoneyStorageXML::writeFileInformation(QDomElement& fileInfo)
+{
+ QDomElement creationDate = m_doc->createElement("CREATION_DATE");
+ creationDate.setAttribute("date", dateToString(m_storage->creationDate()));
+ fileInfo.appendChild(creationDate);
+
+ QDomElement lastModifiedDate = m_doc->createElement("LAST_MODIFIED_DATE");
+ lastModifiedDate.setAttribute("date", dateToString(m_storage->lastModificationDate()));
+ fileInfo.appendChild(lastModifiedDate);
+
+ QDomElement version = m_doc->createElement("VERSION");
+
+ version.setAttribute("id", "1");
+ fileInfo.appendChild(version);
+
+ QDomElement fixVersion = m_doc->createElement("FIXVERSION");
+ fixVersion.setAttribute("id", m_storage->fileFixVersion());
+ fileInfo.appendChild(fixVersion);
+}
+
+void MyMoneyStorageXML::writeUserInformation(QDomElement& userInfo)
+{
+ MyMoneyPayee user = m_storage->user();
+ userInfo.setAttribute("name", user.name());
+ userInfo.setAttribute("email", user.email());
+
+ QDomElement address = m_doc->createElement("ADDRESS");
+ address.setAttribute("street", user.address());
+ address.setAttribute("city", user.city());
+ address.setAttribute("county", user.state());
+ address.setAttribute("zipcode", user.postcode());
+ address.setAttribute("telephone", user.telephone());
+
+ userInfo.appendChild(address);
+}
+
+bool MyMoneyStorageXML::readUserInformation(const QDomElement& userElement)
+{
+ bool rc = true;
+ signalProgress(0, 1, i18n("Loading user information..."));
+
+ MyMoneyPayee user;
+ user.setName(QStringEmpty(userElement.attribute("name")));
+ user.setEmail(QStringEmpty(userElement.attribute("email")));
+
+ QDomElement addressNode = findChildElement("ADDRESS", userElement);
+ if(!addressNode.isNull()) {
+ user.setAddress(QStringEmpty(addressNode.attribute("street")));
+ user.setCity(QStringEmpty(addressNode.attribute("city")));
+ user.setState(QStringEmpty(addressNode.attribute("county")));
+ user.setPostcode(QStringEmpty(addressNode.attribute("zipcode")));
+ user.setTelephone(QStringEmpty(addressNode.attribute("telephone")));
+ }
+
+ m_storage->setUser(user);
+ signalProgress(1, 0);
+
+ return rc;
+}
+
+void MyMoneyStorageXML::writeInstitutions(QDomElement& institutions)
+{
+ const QValueList<MyMoneyInstitution> list = m_storage->institutionList();
+ QValueList<MyMoneyInstitution>::ConstIterator it;
+ institutions.setAttribute("count", list.count());
+
+ for(it = list.begin(); it != list.end(); ++it)
+ writeInstitution(institutions, *it);
+}
+
+void MyMoneyStorageXML::writeInstitution(QDomElement& institution, const MyMoneyInstitution& i)
+{
+ i.writeXML(*m_doc, institution);
+}
+
+void MyMoneyStorageXML::writePayees(QDomElement& payees)
+{
+ const QValueList<MyMoneyPayee> list = m_storage->payeeList();
+ QValueList<MyMoneyPayee>::ConstIterator it;
+ payees.setAttribute("count", list.count());
+
+ for(it = list.begin(); it != list.end(); ++it)
+ writePayee(payees, *it);
+}
+
+void MyMoneyStorageXML::writePayee(QDomElement& payee, const MyMoneyPayee& p)
+{
+ p.writeXML(*m_doc, payee);
+}
+
+void MyMoneyStorageXML::writeAccounts(QDomElement& accounts)
+{
+ QValueList<MyMoneyAccount> list;
+ m_storage->accountList(list);
+ QValueList<MyMoneyAccount>::ConstIterator it;
+ accounts.setAttribute("count", list.count()+5);
+
+ writeAccount(accounts, m_storage->asset());
+ writeAccount(accounts, m_storage->liability());
+ writeAccount(accounts, m_storage->expense());
+ writeAccount(accounts, m_storage->income());
+ writeAccount(accounts, m_storage->equity());
+
+ signalProgress(0, list.count(), i18n("Saving accounts..."));
+ int i = 0;
+ for(it = list.begin(); it != list.end(); ++it, ++i) {
+ writeAccount(accounts, *it);
+ signalProgress(i, 0);
+ }
+}
+
+void MyMoneyStorageXML::writeAccount(QDomElement& account, const MyMoneyAccount& p)
+{
+ p.writeXML(*m_doc, account);
+}
+
+void MyMoneyStorageXML::writeTransactions(QDomElement& transactions)
+{
+ MyMoneyTransactionFilter filter;
+ filter.setReportAllSplits(false);
+ QValueList<MyMoneyTransaction> list;
+ m_storage->transactionList(list, filter);
+ transactions.setAttribute("count", list.count());
+
+ QValueList<MyMoneyTransaction>::ConstIterator it;
+
+ signalProgress(0, list.count(), i18n("Saving transactions..."));
+
+ int i = 0;
+ for(it = list.begin(); it != list.end(); ++it, ++i)
+ {
+ writeTransaction(transactions, *it);
+ signalProgress(i, 0);
+ }
+}
+
+void MyMoneyStorageXML::writeTransaction(QDomElement& transaction, const MyMoneyTransaction& tx)
+{
+ tx.writeXML(*m_doc, transaction);
+}
+
+void MyMoneyStorageXML::writeSchedules(QDomElement& scheduled)
+{
+ const QValueList<MyMoneySchedule> list = m_storage->scheduleList();
+ QValueList<MyMoneySchedule>::ConstIterator it;
+ scheduled.setAttribute("count", list.count());
+
+ for(it = list.begin(); it != list.end(); ++it)
+ {
+ this->writeSchedule(scheduled, *it);
+ }
+}
+
+void MyMoneyStorageXML::writeSchedule(QDomElement& scheduledTx, const MyMoneySchedule& tx)
+{
+ tx.writeXML(*m_doc, scheduledTx);
+}
+
+void MyMoneyStorageXML::writeSecurities(QDomElement& equities)
+{
+ const QValueList<MyMoneySecurity> securityList = m_storage->securityList();
+ equities.setAttribute("count", securityList.count());
+ if(securityList.size())
+ {
+ for(QValueList<MyMoneySecurity>::ConstIterator it = securityList.begin(); it != securityList.end(); ++it)
+ {
+ writeSecurity(equities, (*it));
+ }
+ }
+}
+
+void MyMoneyStorageXML::writeSecurity(QDomElement& securityElement, const MyMoneySecurity& security)
+{
+ security.writeXML(*m_doc, securityElement);
+}
+
+void MyMoneyStorageXML::writeCurrencies(QDomElement& currencies)
+{
+ const QValueList<MyMoneySecurity> currencyList = m_storage->currencyList();
+ currencies.setAttribute("count", currencyList.count());
+ if(currencyList.size())
+ {
+ for(QValueList<MyMoneySecurity>::ConstIterator it = currencyList.begin(); it != currencyList.end(); ++it)
+ {
+ writeSecurity(currencies, (*it));
+ }
+ }
+}
+
+void MyMoneyStorageXML::writeReports(QDomElement& parent)
+{
+ const QValueList<MyMoneyReport> list = m_storage->reportList();
+ QValueList<MyMoneyReport>::ConstIterator it;
+ parent.setAttribute("count", list.count());
+
+ signalProgress(0, list.count(), i18n("Saving reports..."));
+ unsigned i = 0;
+ for(it = list.begin(); it != list.end(); ++it)
+ {
+ (*it).writeXML(*m_doc, parent);
+ signalProgress(++i, 0);
+ }
+}
+
+void MyMoneyStorageXML::writeBudgets(QDomElement& parent)
+{
+ const QValueList<MyMoneyBudget> list = m_storage->budgetList();
+ QValueList<MyMoneyBudget>::ConstIterator it;
+ parent.setAttribute("count", list.count());
+
+ signalProgress(0, list.count(), i18n("Saving budgets..."));
+ unsigned i = 0;
+ for(it = list.begin(); it != list.end(); ++it)
+ {
+ writeBudget(parent, (*it));
+ signalProgress(++i, 0);
+ }
+}
+
+void MyMoneyStorageXML::writeBudget(QDomElement& budget, const MyMoneyBudget& b)
+{
+ b.writeXML(*m_doc, budget);
+}
+
+
+QDomElement MyMoneyStorageXML::findChildElement(const QString& name, const QDomElement& root)
+{
+ QDomNode child = root.firstChild();
+ while(!child.isNull())
+ {
+ if(child.isElement())
+ {
+ QDomElement childElement = child.toElement();
+ if(name == childElement.tagName())
+ {
+ return childElement;
+ }
+ }
+
+ child = child.nextSibling();
+ }
+ return QDomElement();
+}
+
+QDomElement MyMoneyStorageXML::writeKeyValuePairs(const QMap<QString, QString> pairs)
+{
+ if(m_doc)
+ {
+ QDomElement keyValPairs = m_doc->createElement("KEYVALUEPAIRS");
+
+ QMap<QString, QString>::const_iterator it;
+ for(it = pairs.begin(); it != pairs.end(); ++it)
+ {
+ QDomElement pair = m_doc->createElement("PAIR");
+ pair.setAttribute("key", it.key());
+ pair.setAttribute("value", it.data());
+ keyValPairs.appendChild(pair);
+ }
+ return keyValPairs;
+ }
+ return QDomElement();
+}
+
+void MyMoneyStorageXML::writePrices(QDomElement& prices)
+{
+ const MyMoneyPriceList list = m_storage->priceList();
+ MyMoneyPriceList::ConstIterator it;
+ prices.setAttribute("count", list.count());
+
+ for(it = list.begin(); it != list.end(); ++it)
+ {
+ QDomElement price = m_doc->createElement("PRICEPAIR");
+ price.setAttribute("from", it.key().first);
+ price.setAttribute("to", it.key().second);
+ writePricePair(price, *it);
+ prices.appendChild(price);
+ }
+}
+
+void MyMoneyStorageXML::writePricePair(QDomElement& price, const MyMoneyPriceEntries& p)
+{
+ MyMoneyPriceEntries::ConstIterator it;
+ for(it = p.begin(); it != p.end(); ++it) {
+ QDomElement entry = m_doc->createElement("PRICE");
+ writePrice(entry, *it);
+ price.appendChild(entry);
+ }
+}
+
+void MyMoneyStorageXML::writePrice(QDomElement& price, const MyMoneyPrice& p)
+{
+ price.setAttribute("date", p.date().toString(Qt::ISODate));
+ price.setAttribute("price", p.rate(QString()).toString());
+ price.setAttribute("source", p.source());
+}
+
+void MyMoneyStorageXML::setProgressCallback(void(*callback)(int, int, const QString&))
+{
+ m_progressCallback = callback;
+}
+
+void MyMoneyStorageXML::signalProgress(int current, int total, const QString& msg)
+{
+ if(m_progressCallback != 0)
+ (*m_progressCallback)(current, total, msg);
+}
+
+/*!
+ This convenience function returns all of the remaining data in the
+ device.
+
+ @note It's copied from the original Qt sources and modified to
+ fix a problem with KFilterDev that does not correctly return
+ atEnd() status in certain circumstances which caused our
+ application to lock at startup.
+*/
+QByteArray QIODevice::readAll()
+{
+ if ( isDirectAccess() ) {
+ // we know the size
+ int n = size()-at(); // ### fix for 64-bit or large files?
+ int totalRead = 0;
+ QByteArray ba( n );
+ char* c = ba.data();
+ while ( n ) {
+ int r = readBlock( c, n );
+ if ( r < 0 )
+ return QByteArray();
+ n -= r;
+ c += r;
+ totalRead += r;
+ // If we have a translated file, then it is possible that
+ // we read less bytes than size() reports
+ if ( atEnd() ) {
+ ba.resize( totalRead );
+ break;
+ }
+ }
+ return ba;
+ } else {
+ // read until we reach the end
+ const int blocksize = 512;
+ int nread = 0;
+ QByteArray ba;
+ int r = 1;
+ while ( !atEnd() && r != 0) {
+ ba.resize( nread + blocksize );
+ r = readBlock( ba.data()+nread, blocksize );
+ if ( r < 0 )
+ return QByteArray();
+ nread += r;
+ }
+ ba.resize( nread );
+ return ba;
+ }
+}
+
diff --git a/kmymoney2/mymoney/storage/mymoneystoragexml.h b/kmymoney2/mymoney/storage/mymoneystoragexml.h
new file mode 100644
index 0000000..8485978
--- /dev/null
+++ b/kmymoney2/mymoney/storage/mymoneystoragexml.h
@@ -0,0 +1,156 @@
+/***************************************************************************
+ mymoneystoragexml.h - description
+ -------------------
+ begin : Thu Oct 24 2002
+ copyright : (C) 2002 by Kevin Tambascio
+ (C) 2004 by Thomas Baumgart
+ email : Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 MYMONEYSTORAGEXML_H
+#define MYMONEYSTORAGEXML_H
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qdom.h>
+#include <qdatastream.h>
+class QIODevice;
+
+// ----------------------------------------------------------------------------
+// Project Includes
+
+#include "imymoneyserialize.h"
+#include "imymoneystorageformat.h"
+class MyMoneyXmlContentHandler;
+
+/**
+ *@author Kevin Tambascio (ktambascio@users.sourceforge.net)
+ */
+
+#define VERSION_0_60_XML 0x10000010 // Version 0.5 file version info
+#define VERSION_0_61_XML 0x10000011 // use 8 bytes for MyMoneyMoney objects
+
+class MyMoneyStorageXML : public IMyMoneyStorageFormat
+{
+ friend class MyMoneyXmlContentHandler;
+public:
+ MyMoneyStorageXML();
+ virtual ~MyMoneyStorageXML();
+
+ enum fileVersionDirectionType {
+ Reading = 0, /**< version of file to be read */
+ Writing = 1 /**< version to be used when writing a file */
+ };
+
+protected:
+ void setProgressCallback(void(*callback)(int, int, const QString&));
+ void signalProgress(int current, int total, const QString& = "");
+
+ /**
+ * This method returns the version of the underlying file. It
+ * is used by the MyMoney objects contained in a MyMoneyStorageBin object (e.g.
+ * MyMoneyAccount, MyMoneyInstitution, MyMoneyTransaction, etc.) to
+ * determine the layout used when reading/writing a persistant file.
+ * A parameter is used to determine the direction.
+ *
+ * @param dir information about the direction (reading/writing). The
+ * default is reading.
+ *
+ * @return version QString of file's version
+ *
+ * @see m_fileVersionRead, m_fileVersionWrite
+ */
+ static unsigned int fileVersion(fileVersionDirectionType dir = Reading);
+
+ QValueList<QDomElement> readElements(QString groupTag, QString itemTag = QString());
+
+ bool readFileInformation(const QDomElement& fileInfo);
+ virtual void writeFileInformation(QDomElement& fileInfo);
+
+ virtual void writeUserInformation(QDomElement& userInfo);
+
+ virtual void writeInstitution(QDomElement& institutions, const MyMoneyInstitution& i);
+ virtual void writeInstitutions(QDomElement& institutions);
+
+ virtual void writePrices(QDomElement& prices);
+ virtual void writePricePair(QDomElement& price, const MyMoneyPriceEntries& p);
+ virtual void writePrice(QDomElement& prices, const MyMoneyPrice& p);
+
+ virtual void writePayees(QDomElement& payees);
+ virtual void writePayee(QDomElement& payees, const MyMoneyPayee& p);
+
+ virtual void writeAccounts(QDomElement& accounts);
+ virtual void writeAccount(QDomElement& accounts, const MyMoneyAccount& p);
+
+ virtual void writeTransactions(QDomElement& transactions);
+ virtual void writeTransaction(QDomElement& transactions, const MyMoneyTransaction& tx);
+
+ virtual void writeSchedules(QDomElement& scheduled);
+ virtual void writeSchedule(QDomElement& scheduledTx, const MyMoneySchedule& tx);
+
+ virtual void writeReports(QDomElement& e);
+ virtual void writeBudgets(QDomElement& e);
+ virtual void writeBudget(QDomElement& budget, const MyMoneyBudget& b);
+
+ virtual void writeSecurities(QDomElement& securities);
+ virtual void writeSecurity(QDomElement& securityElement, const MyMoneySecurity& security);
+
+ virtual void writeCurrencies(QDomElement& currencies);
+
+ virtual QDomElement writeKeyValuePairs(const QMap<QString, QString> pairs);
+
+ virtual void readFile(QIODevice* s, IMyMoneySerialize* storage);
+ virtual void writeFile(QIODevice* s, IMyMoneySerialize* storage);
+
+ bool readUserInformation(const QDomElement& userElement);
+
+ void readPricePair(const QDomElement& pricePair);
+ const MyMoneyPrice readPrice(const QString& from, const QString& to, const QDomElement& price);
+
+ QDomElement findChildElement(const QString& name, const QDomElement& root);
+
+private:
+ void (*m_progressCallback)(int, int, const QString&);
+
+protected:
+ IMyMoneySerialize *m_storage;
+ QDomDocument *m_doc;
+
+private:
+ /// \internal d-pointer class.
+ class Private;
+ /// \internal d-pointer instance.
+ Private* const d;
+
+ /**
+ * This member is used to store the file version information
+ * obtained while reading a file.
+ */
+ static unsigned int fileVersionRead;
+
+ /**
+ * This member is used to store the file version information
+ * to be used when writing a file.
+ */
+ static unsigned int fileVersionWrite;
+ /**
+ * This member keeps the id of the base currency. We need this
+ * temporarily to convert the price history from the old to the
+ * new format. This should go at some time beyond 0.8 (ipwizard)
+ */
+ QString m_baseCurrencyId;
+
+};
+
+#endif