diff options
Diffstat (limited to 'kmymoney2/mymoney/storage')
27 files changed, 19277 insertions, 0 deletions
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 |